diff --git a/.backportrc.json b/.backportrc.json index 94c2549418f171..dbf40e472325e1 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -3,6 +3,7 @@ "repoName": "kibana", "targetBranchChoices": [ "main", + "8.6", "8.5", "8.4", "8.3", @@ -42,7 +43,7 @@ "backport" ], "branchLabelMapping": { - "^v8.6.0$": "main", + "^v8.7.0$": "main", "^v(\\d+).(\\d+).\\d+$": "$1.$2" }, "autoMerge": true, diff --git a/.buildkite/scripts/steps/build_api_docs.sh b/.buildkite/scripts/steps/build_api_docs.sh index 185d8ec09aa5a7..f86032c902d1ae 100755 --- a/.buildkite/scripts/steps/build_api_docs.sh +++ b/.buildkite/scripts/steps/build_api_docs.sh @@ -24,7 +24,7 @@ if [[ "${PUBLISH_API_DOCS_CHANGES:-}" == "true" ]]; then git remote add kibanamachine https://github.com/kibanamachine/kibana.git git push -u kibanamachine "$branch" - prUrl=$(gh pr create --repo elastic/kibana --title "[api-docs] $(date +%F) Daily api_docs build" --body "Generated by $BUILDKITE_BUILD_URL" --label "release_note:skip" --label "backport:auto-version") + prUrl=$(gh pr create --repo elastic/kibana --title "[api-docs] $(date +%F) Daily api_docs build" --body "Generated by $BUILDKITE_BUILD_URL" --label "release_note:skip" --label "docs") echo "Opened PR: $prUrl" gh pr merge --repo elastic/kibana --auto --squash "$prUrl" diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 2a37b65a8cd657..d814ce088e8397 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 0a6208c271abf5..dd334be41d09bf 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.devdocs.json b/api_docs/aiops.devdocs.json index 52234380bb0a69..6ee28ad0d4fa69 100644 --- a/api_docs/aiops.devdocs.json +++ b/api_docs/aiops.devdocs.json @@ -3,6 +3,47 @@ "client": { "classes": [], "functions": [ + { + "parentPluginId": "aiops", + "id": "def-public.ChangePointDetection", + "type": "Function", + "tags": [], + "label": "ChangePointDetection", + "description": [ + "\nLazy-wrapped LogCategorizationAppStateProps React component" + ], + "signature": [ + "(props: React.PropsWithChildren<", + "LogCategorizationAppStateProps", + ">) => JSX.Element" + ], + "path": "x-pack/plugins/aiops/public/shared_lazy_components.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "aiops", + "id": "def-public.ChangePointDetection.$1", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [ + "- properties specifying the data on which to run the analysis." + ], + "signature": [ + "React.PropsWithChildren<", + "LogCategorizationAppStateProps", + ">" + ], + "path": "x-pack/plugins/aiops/public/shared_lazy_components.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "aiops", "id": "def-public.ExplainLogRateSpikes", @@ -154,6 +195,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "aiops", + "id": "def-common.CHANGE_POINT_DETECTION_ENABLED", + "type": "boolean", + "tags": [], + "label": "CHANGE_POINT_DETECTION_ENABLED", + "description": [], + "signature": [ + "false" + ], + "path": "x-pack/plugins/aiops/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "aiops", "id": "def-common.PLUGIN_ID", diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index e69cd3fe98487a..d7496b410b75ef 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.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 | |-------------------|-----------|------------------------|-----------------| -| 9 | 0 | 0 | 2 | +| 12 | 0 | 1 | 2 | ## Client diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index bf12630765b133..96a9d658f0a884 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -66,7 +66,7 @@ "section": "def-common.RuleAction", "text": "RuleAction" }, - "[]; throttle: string | null; consumer: string; alertTypeId: string; schedule: ", + "[]; throttle?: string | null | undefined; consumer: string; alertTypeId: string; schedule: ", { "pluginId": "alerting", "scope": "common", @@ -82,7 +82,7 @@ "section": "def-common.MappedParams", "text": "MappedParams" }, - " | undefined; scheduledTaskId?: string | undefined; createdBy: string | null; updatedBy: string | null; createdAt: Date; updatedAt: Date; apiKeyOwner: string | null; muteAll: boolean; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\" | null; mutedInstanceIds: string[]; executionStatus: ", + " | undefined; scheduledTaskId?: string | undefined; createdBy: string | null; updatedBy: string | null; createdAt: Date; updatedAt: Date; apiKeyOwner: string | null; muteAll: boolean; notifyWhen?: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\" | null | undefined; mutedInstanceIds: string[]; executionStatus: ", { "pluginId": "alerting", "scope": "common", @@ -2761,7 +2761,7 @@ "section": "def-common.IntervalSchedule", "text": "IntervalSchedule" }, - "; } | { operation: \"set\"; field: \"throttle\"; value: string | null; } | { operation: \"set\"; field: \"notifyWhen\"; value: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\" | null; } | { operation: \"set\"; field: \"snoozeSchedule\"; value: ", + "; } | { operation: \"set\"; field: \"throttle\"; value: string | null | undefined; } | { operation: \"set\"; field: \"notifyWhen\"; value: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\" | null | undefined; } | { operation: \"set\"; field: \"snoozeSchedule\"; value: ", { "pluginId": "alerting", "scope": "common", @@ -2994,7 +2994,23 @@ "section": "def-common.ResolvedSanitizedRule", "text": "ResolvedSanitizedRule" }, - ">; enable: ({ id }: { id: string; }) => Promise; disable: ({ id }: { id: string; }) => Promise; muteAll: ({ id }: { id: string; }) => Promise; getAlertState: ({ id }: { id: string; }) => Promise; getAlertSummary: ({ id, dateStart, numberOfExecutions, }: ", + ">; enable: ({ id }: { id: string; }) => Promise; disable: ({ id }: { id: string; }) => Promise; clone: (id: string, { newId }: { newId?: string | undefined; }) => Promise<", + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.SanitizedRule", + "text": "SanitizedRule" + }, + ">; muteAll: ({ id }: { id: string; }) => Promise; getAlertState: ({ id }: { id: string; }) => Promise; getAlertSummary: ({ id, dateStart, numberOfExecutions, }: ", "GetAlertSummaryParams", ") => Promise<", { @@ -3049,7 +3065,7 @@ ") => 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; }>; bulkDeleteRules: (options: ", - "BulkDeleteOptions", + "BulkOptions", ") => Promise<{ errors: ", { "pluginId": "alerting", @@ -3090,7 +3106,17 @@ "section": "def-server.BulkOperationError", "text": "BulkOperationError" }, - "[]; total: number; }>; updateApiKey: ({ id }: { id: string; }) => Promise; snooze: ({ id, snoozeSchedule, }: { id: string; snoozeSchedule: ", + "[]; total: number; }>; bulkEnableRules: (options: ", + "BulkOptions", + ") => Promise<{ errors: ", + { + "pluginId": "alerting", + "scope": "server", + "docId": "kibAlertingPluginApi", + "section": "def-server.BulkOperationError", + "text": "BulkOperationError" + }, + "[]; total: number; taskIdsFailedToBeEnabled: string[]; }>; updateApiKey: ({ id }: { id: string; }) => Promise; snooze: ({ id, snoozeSchedule, }: { id: string; snoozeSchedule: ", { "pluginId": "alerting", "scope": "common", @@ -3897,7 +3923,7 @@ "label": "throttle", "description": [], "signature": [ - "string | null" + "string | null | undefined" ], "path": "x-pack/plugins/alerting/common/alert_summary.ts", "deprecated": false, @@ -5038,7 +5064,7 @@ "label": "throttle", "description": [], "signature": [ - "string | null" + "string | null | undefined" ], "path": "x-pack/plugins/alerting/common/rule.ts", "deprecated": false, @@ -5063,7 +5089,7 @@ "label": "notifyWhen", "description": [], "signature": [ - "\"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\" | null" + "\"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\" | null | undefined" ], "path": "x-pack/plugins/alerting/common/rule.ts", "deprecated": false, @@ -5274,6 +5300,20 @@ "path": "x-pack/plugins/alerting/common/rule.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "alerting", + "id": "def-common.RuleAction.frequency", + "type": "Object", + "tags": [], + "label": "frequency", + "description": [], + "signature": [ + "{ summary: boolean; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; throttle: string | null; } | undefined" + ], + "path": "x-pack/plugins/alerting/common/rule.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -6967,7 +7007,7 @@ "section": "def-common.RuleAction", "text": "RuleAction" }, - "[]; throttle: string | null; consumer: string; alertTypeId: string; schedule: ", + "[]; throttle?: string | null | undefined; consumer: string; alertTypeId: string; schedule: ", { "pluginId": "alerting", "scope": "common", @@ -6983,7 +7023,7 @@ "section": "def-common.MappedParams", "text": "MappedParams" }, - " | undefined; scheduledTaskId?: string | undefined; createdBy: string | null; updatedBy: string | null; createdAt: Date; updatedAt: Date; apiKeyOwner: string | null; muteAll: boolean; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\" | null; mutedInstanceIds: string[]; executionStatus: ", + " | undefined; scheduledTaskId?: string | undefined; createdBy: string | null; updatedBy: string | null; createdAt: Date; updatedAt: Date; apiKeyOwner: string | null; muteAll: boolean; notifyWhen?: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\" | null | undefined; mutedInstanceIds: string[]; executionStatus: ", { "pluginId": "alerting", "scope": "common", @@ -7286,6 +7326,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-common.RuleNotifyWhenTypeValues", + "type": "Object", + "tags": [], + "label": "RuleNotifyWhenTypeValues", + "description": [], + "signature": [ + "readonly [\"onActionGroupChange\", \"onActiveAlert\", \"onThrottleInterval\"]" + ], + "path": "x-pack/plugins/alerting/common/rule_notify_when_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-common.ruleParamsSchema", diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index ddd56a8934110f..008273792d02fc 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.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 | |-------------------|-----------|------------------------|-----------------| -| 415 | 0 | 406 | 27 | +| 417 | 0 | 408 | 27 | ## Client diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index d47eb8ba2ab51a..daf3593733b1b9 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -793,7 +793,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/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\" | \"POST /internal/apm/traces/aggregated_critical_path\" | \"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\" | \"GET /internal/apm/get_agents_per_service\" | \"GET /internal/apm/services/{serviceName}/agent_instances\" | \"GET /internal/apm/services/{serviceName}/mobile/filters\"" + "\"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\" | \"POST /internal/apm/traces/aggregated_critical_path\" | \"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\" | \"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\" | \"GET /internal/apm/get_agents_per_service\" | \"GET /internal/apm/services/{serviceName}/agent_instances\" | \"GET /internal/apm/services/{serviceName}/mobile/filters\"" ], "path": "x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -2199,6 +2199,8 @@ "StringC", "; transactionType: ", "StringC", + "; samplerShardSize: ", + "StringC", "; }>, ", "TypeC", "<{ environment: ", @@ -2251,74 +2253,6 @@ "TopValuesStats", ", ", "APMRouteCreateOptions", - ">; \"POST /internal/apm/correlations/field_stats/transactions\": ", - { - "pluginId": "@kbn/server-route-repository", - "scope": "common", - "docId": "kibKbnServerRouteRepositoryPluginApi", - "section": "def-common.ServerRoute", - "text": "ServerRoute" - }, - "<\"POST /internal/apm/correlations/field_stats/transactions\", ", - "TypeC", - "<{ body: ", - "IntersectionC", - "<[", - "PartialC", - "<{ serviceName: ", - "StringC", - "; transactionName: ", - "StringC", - "; transactionType: ", - "StringC", - "; }>, ", - "TypeC", - "<{ fieldsToSample: ", - "ArrayC", - "<", - "StringC", - ">; }>, ", - "TypeC", - "<{ environment: ", - "UnionC", - "<[", - "LiteralC", - "<\"ENVIRONMENT_NOT_DEFINED\">, ", - "LiteralC", - "<\"ENVIRONMENT_ALL\">, ", - "BrandC", - "<", - "StringC", - ", ", - { - "pluginId": "@kbn/io-ts-utils", - "scope": "common", - "docId": "kibKbnIoTsUtilsPluginApi", - "section": "def-common.NonEmptyStringBrand", - "text": "NonEmptyStringBrand" - }, - ">]>; }>, ", - "TypeC", - "<{ kuery: ", - "StringC", - "; }>, ", - "TypeC", - "<{ start: ", - "Type", - "; end: ", - "Type", - "; }>]>; }>, ", - { - "pluginId": "apm", - "scope": "server", - "docId": "kibApmPluginApi", - "section": "def-server.APMRouteHandlerResources", - "text": "APMRouteHandlerResources" - }, - ", { stats: ", - "FieldStats", - "[]; errors: any[]; }, ", - "APMRouteCreateOptions", ">; \"GET /internal/apm/correlations/field_candidates/transactions\": ", { "pluginId": "@kbn/server-route-repository", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index f82318ef405eb4..2cac2f885c74d7 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-11-15 +date: 2022-11-17 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 | |-------------------|-----------|------------------------|-----------------| -| 41 | 0 | 41 | 58 | +| 41 | 0 | 41 | 57 | ## Client diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 9e2b34b08519fb..2e82b17cdcd7bb 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-11-15 +date: 2022-11-17 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 ce9a5d8ebb782d..1fc795de52b869 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-11-15 +date: 2022-11-17 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 45ad3174d65862..be898589ab710b 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-11-15 +date: 2022-11-17 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 d0119059deb373..43dc0d6a7a65d7 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-11-15 +date: 2022-11-17 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 9f1e5197fe3bc0..9f9a248e8ae3d6 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-11-15 +date: 2022-11-17 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 c99cd59b6cb209..a444c90bc5e3df 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-11-15 +date: 2022-11-17 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 2a59c1d710e97a..e041c539996c59 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-11-15 +date: 2022-11-17 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 b57c31af62e578..a8535f8fa505ed 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-11-15 +date: 2022-11-17 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 aa0f0be5276600..c5c0e7694e7964 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-11-15 +date: 2022-11-17 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 9a582352c4ba69..0b60075396e8cd 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 3ab49c006af55e..f0585d28402ca7 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index 139cde01e6e827..b4ddda1a26b0bd 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -7860,12 +7860,12 @@ "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" }, { - "plugin": "security", - "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + "plugin": "@kbn/core-root-server-internal", + "path": "packages/core/root/core-root-server-internal/src/server.ts" }, { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" }, { "plugin": "dashboard", @@ -7875,6 +7875,10 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" + }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -8739,6 +8743,24 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-public.IAnalyticsClient.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nForces all shippers to send all their enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "core", "id": "def-public.IAnalyticsClient.shutdown", @@ -8746,10 +8768,10 @@ "tags": [], "label": "shutdown", "description": [ - "\nStops the client." + "\nStops the client. Flushing any pending events in the process." ], "signature": [ - "() => void" + "() => Promise" ], "path": "packages/analytics/client/src/analytics_client/types.ts", "deprecated": false, @@ -9478,6 +9500,24 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-public.IShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nSends all the enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "core", "id": "def-public.IShipper.shutdown", @@ -12822,22 +12862,6 @@ "plugin": "embeddable", "path": "src/plugins/embeddable/public/types.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/epm.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/epm.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/settings.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/settings.ts" - }, { "plugin": "visualizations", "path": "src/plugins/visualizations/public/utils/saved_objects_utils/save_with_confirmation.ts" @@ -12918,6 +12942,22 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/epm.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/epm.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/settings.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/settings.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx" @@ -13182,10 +13222,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/types.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts" @@ -13290,14 +13326,6 @@ "plugin": "savedSearch", "path": "src/plugins/saved_search/server/saved_objects/search_migrations.ts" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, { "plugin": "visualizations", "path": "src/plugins/visualizations/common/types.ts" @@ -13338,6 +13366,14 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts" @@ -31157,12 +31193,12 @@ "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" }, { - "plugin": "security", - "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + "plugin": "@kbn/core-root-server-internal", + "path": "packages/core/root/core-root-server-internal/src/server.ts" }, { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" }, { "plugin": "dashboard", @@ -31172,6 +31208,10 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" + }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -32036,6 +32076,24 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-server.IAnalyticsClient.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nForces all shippers to send all their enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "core", "id": "def-server.IAnalyticsClient.shutdown", @@ -32043,10 +32101,10 @@ "tags": [], "label": "shutdown", "description": [ - "\nStops the client." + "\nStops the client. Flushing any pending events in the process." ], "signature": [ - "() => void" + "() => Promise" ], "path": "packages/analytics/client/src/analytics_client/types.ts", "deprecated": false, @@ -41120,6 +41178,24 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-server.IShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nSends all the enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "core", "id": "def-server.IShipper.shutdown", @@ -47167,22 +47243,6 @@ "plugin": "embeddable", "path": "src/plugins/embeddable/public/types.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/epm.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/epm.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/settings.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/settings.ts" - }, { "plugin": "visualizations", "path": "src/plugins/visualizations/public/utils/saved_objects_utils/save_with_confirmation.ts" @@ -47263,6 +47323,22 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/epm.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/epm.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/settings.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/settings.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx" @@ -47527,10 +47603,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/types.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts" @@ -47635,14 +47707,6 @@ "plugin": "savedSearch", "path": "src/plugins/saved_search/server/saved_objects/search_migrations.ts" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, { "plugin": "visualizations", "path": "src/plugins/visualizations/common/types.ts" @@ -47683,6 +47747,14 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/saved_objects/migrations/7.11/index.ts" diff --git a/api_docs/core.mdx b/api_docs/core.mdx index cc4c537605ef60..17db21e2dced9e 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-11-15 +date: 2022-11-17 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 | |-------------------|-----------|------------------------|-----------------| -| 2704 | 17 | 1202 | 0 | +| 2708 | 17 | 1202 | 0 | ## Client diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index d0b9804daec047..e48fc4111b89aa 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-11-15 +date: 2022-11-17 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 17d55d9e89d55d..29be245ff204b9 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-11-15 +date: 2022-11-17 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 d5be614cce1500..7f308f473b9bcc 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-11-15 +date: 2022-11-17 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 efdc1b711138b3..d43e617968dbeb 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -12455,10 +12455,18 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/data/data_service.ts" }, + { + "plugin": "dataVisualizer", + "path": "x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx" + }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx" }, + { + "plugin": "dataVisualizer", + "path": "x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx" + }, { "plugin": "stackAlerts", "path": "x-pack/plugins/stack_alerts/public/rule_types/threshold/expression.tsx" @@ -13147,6 +13155,22 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" }, + { + "plugin": "controls", + "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" + }, + { + "plugin": "controls", + "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" + }, + { + "plugin": "lens", + "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" + }, + { + "plugin": "lens", + "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" + }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" @@ -13187,26 +13211,6 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, - { - "plugin": "presentationUtil", - "path": "src/plugins/presentation_util/public/services/storybook/data_views.ts" - }, - { - "plugin": "controls", - "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" - }, - { - "plugin": "controls", - "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" - }, { "plugin": "observability", "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" @@ -13735,6 +13739,10 @@ "plugin": "inputControlVis", "path": "src/plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts" }, + { + "plugin": "presentationUtil", + "path": "src/plugins/presentation_util/public/services/data_views/data_views.story.ts" + }, { "plugin": "visTypeTimelion", "path": "src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts" @@ -20841,6 +20849,22 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" }, + { + "plugin": "controls", + "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" + }, + { + "plugin": "controls", + "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" + }, + { + "plugin": "lens", + "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" + }, + { + "plugin": "lens", + "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" + }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" @@ -20881,26 +20905,6 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, - { - "plugin": "presentationUtil", - "path": "src/plugins/presentation_util/public/services/storybook/data_views.ts" - }, - { - "plugin": "controls", - "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" - }, - { - "plugin": "controls", - "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" - }, { "plugin": "observability", "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" @@ -21429,6 +21433,10 @@ "plugin": "inputControlVis", "path": "src/plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts" }, + { + "plugin": "presentationUtil", + "path": "src/plugins/presentation_util/public/services/data_views/data_views.story.ts" + }, { "plugin": "visTypeTimelion", "path": "src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts" diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 148f2d7770cf29..b77bc0cb5cf7c2 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index d97a40733c1c80..9a469733d0ac8f 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index cf40c2f308e713..e3377e86496685 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index d72c7980d7fd43..10b91c218240ad 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-11-15 +date: 2022-11-17 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 885597ac5bd372..455b60fc189536 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-11-15 +date: 2022-11-17 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 12b8cbbd2ad529..a0ff9616b7726d 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-11-15 +date: 2022-11-17 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 241f620bd832bd..c6ee25493c618d 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -315,6 +315,22 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" }, + { + "plugin": "controls", + "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" + }, + { + "plugin": "controls", + "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" + }, + { + "plugin": "lens", + "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" + }, + { + "plugin": "lens", + "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" + }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" @@ -355,26 +371,6 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, - { - "plugin": "presentationUtil", - "path": "src/plugins/presentation_util/public/services/storybook/data_views.ts" - }, - { - "plugin": "controls", - "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" - }, - { - "plugin": "controls", - "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" - }, { "plugin": "observability", "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" @@ -903,6 +899,10 @@ "plugin": "inputControlVis", "path": "src/plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts" }, + { + "plugin": "presentationUtil", + "path": "src/plugins/presentation_util/public/services/data_views/data_views.story.ts" + }, { "plugin": "visTypeTimelion", "path": "src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts" @@ -8608,6 +8608,22 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" }, + { + "plugin": "controls", + "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" + }, + { + "plugin": "controls", + "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" + }, + { + "plugin": "lens", + "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" + }, + { + "plugin": "lens", + "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" + }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" @@ -8648,26 +8664,6 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, - { - "plugin": "presentationUtil", - "path": "src/plugins/presentation_util/public/services/storybook/data_views.ts" - }, - { - "plugin": "controls", - "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" - }, - { - "plugin": "controls", - "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" - }, { "plugin": "observability", "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" @@ -9196,6 +9192,10 @@ "plugin": "inputControlVis", "path": "src/plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts" }, + { + "plugin": "presentationUtil", + "path": "src/plugins/presentation_util/public/services/data_views/data_views.story.ts" + }, { "plugin": "visTypeTimelion", "path": "src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts" @@ -15982,6 +15982,22 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/public/hooks/use_existing_fields.ts" }, + { + "plugin": "controls", + "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" + }, + { + "plugin": "controls", + "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" + }, + { + "plugin": "lens", + "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" + }, + { + "plugin": "lens", + "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" + }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" @@ -16022,26 +16038,6 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, - { - "plugin": "presentationUtil", - "path": "src/plugins/presentation_util/public/services/storybook/data_views.ts" - }, - { - "plugin": "controls", - "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" - }, - { - "plugin": "controls", - "path": "src/plugins/controls/public/services/options_list/options_list_service.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/data_views_service/loader.ts" - }, - { - "plugin": "lens", - "path": "x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx" - }, { "plugin": "observability", "path": "x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts" @@ -16570,6 +16566,10 @@ "plugin": "inputControlVis", "path": "src/plugins/input_control_vis/public/test_utils/get_index_pattern_mock.ts" }, + { + "plugin": "presentationUtil", + "path": "src/plugins/presentation_util/public/services/data_views/data_views.story.ts" + }, { "plugin": "visTypeTimelion", "path": "src/plugins/vis_types/timelion/public/helpers/arg_value_suggestions.ts" diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 28b1a8eaccd65f..1ac5c68c417042 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 863c83640be277..623362e8a45ff7 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-11-15 +date: 2022-11-17 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 991b4113dadb18..2337e17ebb3739 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -24,14 +24,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | alerting, discover, securitySolution | - | | | stackAlerts, alerting, securitySolution, inputControlVis | - | | | actions, alerting | - | -| | @kbn/core-saved-objects-common, savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | -| | @kbn/core-saved-objects-common, savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | -| | core, savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | +| | @kbn/core-saved-objects-common, savedObjects, embeddable, visualizations, dashboard, fleet, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | +| | @kbn/core-saved-objects-common, savedObjects, embeddable, visualizations, dashboard, fleet, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | +| | core, savedObjects, embeddable, visualizations, dashboard, fleet, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | | | discover, maps, monitoring | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, ml, infra, visTypeTimeseries, apm, presentationUtil, controls, lens, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, controls, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | | | discover | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, ml, infra, visTypeTimeseries, apm, presentationUtil, controls, lens, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, ml, infra, visTypeTimeseries, apm, presentationUtil, controls, lens, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, controls, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, controls, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover | - | | | data, discover, embeddable | - | | | advancedSettings, discover | - | | | advancedSettings, discover | - | @@ -46,7 +46,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | visTypeTimeseries, graph, dataViewManagement, dataViews | - | | | visTypeTimeseries, graph, dataViewManagement, dataViews | - | | | visTypeTimeseries, graph, dataViewManagement | - | -| | dashboard | - | | | observability, dataVisualizer, fleet, cloudSecurityPosture, discoverEnhanced, osquery, synthetics | - | | | dataViewManagement, dataViews | - | | | dataViews, dataViewManagement | - | @@ -150,6 +149,7 @@ Safe to remove. | | expressions | | | expressions | | | expressions | +| | kibanaReact | | | kibanaReact | | | savedObjects | | | savedObjects | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 1a73d13dfac961..248da56308c578 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -216,9 +216,9 @@ so TS and code-reference navigation might not highlight them. | | | [plugin.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.test.ts#:~:text=getKibanaFeatures) | 8.8.0 | | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/plugin.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/license_state.test.ts#:~:text=license%24), [license_state.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/lib/license_state.test.ts#:~:text=license%24) | 8.8.0 | | | [task.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/usage/task.ts#:~:text=index) | - | -| | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes)+ 15 more | - | -| | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes)+ 15 more | - | -| | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes)+ 15 more | - | +| | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes)+ 14 more | - | +| | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes)+ 14 more | - | +| | [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/common/rule.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes), [migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting/server/saved_objects/geo_containment/migrations.ts#:~:text=SavedObjectAttributes)+ 14 more | - | @@ -320,7 +320,6 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| | | [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/data/types.ts#:~:text=fieldFormats), [data_service.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/services/data/data_service.ts#:~:text=fieldFormats) | - | -| | [dashboard_viewport.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx#:~:text=ExitFullScreenButton), [dashboard_viewport.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx#:~:text=ExitFullScreenButton), [dashboard_viewport.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx#:~:text=ExitFullScreenButton) | - | | | [save_modal.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/top_nav/save_modal.tsx#:~:text=SavedObjectSaveModal), [save_modal.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/top_nav/save_modal.tsx#:~:text=SavedObjectSaveModal) | 8.8.0 | | | [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject), [clone_panel_action.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/application/actions/clone_panel_action.tsx#:~:text=SavedObject) | 8.8.0 | | | [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/types.ts#:~:text=onAppLeave), [plugin.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/dashboard/public/plugin.tsx#:~:text=onAppLeave) | 8.8.0 | @@ -395,7 +394,7 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [top_values.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx#:~:text=fieldFormats) | - | +| | [document_stats.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx#:~:text=fieldFormats), [top_values.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx#:~:text=fieldFormats), [choropleth_map.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/choropleth_map.tsx#:~:text=fieldFormats) | - | | | [full_time_range_selector_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts#:~:text=title), [actions_panel.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/actions_panel/actions_panel.tsx#:~:text=title), [use_data_visualizer_grid_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts#:~:text=title), [index_data_visualizer_view.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx#:~:text=title), [full_time_range_selector_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts#:~:text=title), [actions_panel.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/actions_panel/actions_panel.tsx#:~:text=title), [use_data_visualizer_grid_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts#:~:text=title), [index_data_visualizer_view.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx#:~:text=title) | - | | | [full_time_range_selector_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts#:~:text=title), [actions_panel.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/actions_panel/actions_panel.tsx#:~:text=title), [use_data_visualizer_grid_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts#:~:text=title), [index_data_visualizer_view.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx#:~:text=title), [full_time_range_selector_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts#:~:text=title), [actions_panel.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/actions_panel/actions_panel.tsx#:~:text=title), [use_data_visualizer_grid_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts#:~:text=title), [index_data_visualizer_view.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx#:~:text=title) | - | | | [full_time_range_selector_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts#:~:text=title), [actions_panel.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/actions_panel/actions_panel.tsx#:~:text=title), [use_data_visualizer_grid_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts#:~:text=title), [index_data_visualizer_view.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx#:~:text=title) | - | @@ -726,9 +725,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/src/plugins/presentation_util/public/services/storybook/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/services/storybook/data_views.ts#:~:text=title) | - | -| | [data_views.ts](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/services/storybook/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/services/storybook/data_views.ts#:~:text=title) | - | -| | [data_views.ts](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/services/storybook/data_views.ts#:~:text=title) | - | +| | [data_views.story.ts](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/services/data_views/data_views.story.ts#:~:text=title), [data_views.story.ts](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/services/data_views/data_views.story.ts#:~:text=title) | - | +| | [data_views.story.ts](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/services/data_views/data_views.story.ts#:~:text=title), [data_views.story.ts](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/services/data_views/data_views.story.ts#:~:text=title) | - | +| | [data_views.story.ts](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/services/data_views/data_views.story.ts#:~:text=title) | - | | | [saved_object_save_modal_dashboard.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx#:~:text=SavedObjectSaveModal), [saved_object_save_modal_dashboard.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx#:~:text=SavedObjectSaveModal) | 8.8.0 | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index a154e3ef0ba36b..7a000ab3693167 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 50f7bb9384c86c..e53643c0b29322 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 92907ba112459c..c22f77a8af32b4 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-11-15 +date: 2022-11-17 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 db15923caba01c..060c8700b486ca 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 77f1ca49c46d50..bfbd30384abb6b 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index c6dad24412a011..0604f07043ddc7 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-11-15 +date: 2022-11-17 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 9f14b7670e79bd..fbdd11c61ebf2c 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-11-15 +date: 2022-11-17 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 2085d702ea04d2..9f0da634f0b0b3 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-11-15 +date: 2022-11-17 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 9f57cc8f2c9386..c9d364d62896a3 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-11-15 +date: 2022-11-17 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 4414bde59bbc45..6175ea690c0ac1 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 62718e88ba57b2..8edea9c7a7adea 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-11-15 +date: 2022-11-17 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 83c8d739708986..61ea6bb84258c4 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-11-15 +date: 2022-11-17 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 f6691becea10cb..81232417be9af2 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-11-15 +date: 2022-11-17 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 7fed1e27b432e2..cca3d8fe9cd52e 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-11-15 +date: 2022-11-17 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 9a55aab419ec25..ca4dc26a3fa383 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-11-15 +date: 2022-11-17 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 0bef7469ac2196..8d57e53caaf99b 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-11-15 +date: 2022-11-17 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 0cebc7ce6cc152..ac60ab713419d2 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-11-15 +date: 2022-11-17 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 349834f16f26f5..99b592e151bd23 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-11-15 +date: 2022-11-17 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 1fd9053696fdc4..010d8a832b8504 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-11-15 +date: 2022-11-17 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 76b5668bb9c9ee..a8b7e9b8ee202a 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-11-15 +date: 2022-11-17 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 b0b39e4dc7d8a2..8e1edaf45f978c 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-11-15 +date: 2022-11-17 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 a10589ee7d0be0..e1c81c1e2a51bc 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-11-15 +date: 2022-11-17 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 56d41a9d4785df..4a3d3c986d7ad8 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-11-15 +date: 2022-11-17 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 fd4681e96bc92e..3ef715b100007b 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 0512618f6a86f9..c42e3a632aaa88 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 5ccf19338b4c56..795a45ec978a96 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 6a52959f7b5529..996ce185bde27d 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-11-15 +date: 2022-11-17 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 64e9a1addcd52c..698daaa354917e 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-11-15 +date: 2022-11-17 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 b281e8317bbdc1..6ea038e8d71c3a 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -141,10 +141,10 @@ "pluginId": "files", "scope": "public", "docId": "kibFilesPluginApi", - "section": "def-public.Props", - "text": "Props" + "section": "def-public.UploadFileProps", + "text": "UploadFileProps" }, - ") => JSX.Element" + ") => JSX.Element" ], "path": "src/plugins/files/public/components/upload_file/index.tsx", "deprecated": false, @@ -153,7 +153,7 @@ { "parentPluginId": "files", "id": "def-public.UploadFile.$1", - "type": "Object", + "type": "CompoundType", "tags": [], "label": "props", "description": [], @@ -162,10 +162,9 @@ "pluginId": "files", "scope": "public", "docId": "kibFilesPluginApi", - "section": "def-public.Props", - "text": "Props" - }, - "" + "section": "def-public.UploadFileProps", + "text": "UploadFileProps" + } ], "path": "src/plugins/files/public/components/upload_file/index.tsx", "deprecated": false, @@ -905,289 +904,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "files", - "id": "def-public.Props", - "type": "Interface", - "tags": [], - "label": "Props", - "description": [ - "\nUploadFile component props" - ], - "signature": [ - { - "pluginId": "files", - "scope": "public", - "docId": "kibFilesPluginApi", - "section": "def-public.Props", - "text": "Props" - }, - "" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.Props.kind", - "type": "Uncategorized", - "tags": [], - "label": "kind", - "description": [ - "\nA file kind that should be registered during plugin startup. See {@link FileServiceStart}." - ], - "signature": [ - "Kind" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-public.Props.allowClear", - "type": "CompoundType", - "tags": [ - "note" - ], - "label": "allowClear", - "description": [ - "\nAllow users to clear a file after uploading.\n" - ], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-public.Props.immediate", - "type": "CompoundType", - "tags": [], - "label": "immediate", - "description": [ - "\nStart uploading the file as soon as it is provided\nby the user." - ], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-public.Props.meta", - "type": "Object", - "tags": [], - "label": "meta", - "description": [ - "\nMetadata that you want to associate with any uploaded files" - ], - "signature": [ - "Record | undefined" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "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": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-public.Props.allowRepeatedUploads", - "type": "CompoundType", - "tags": [ - "default" - ], - "label": "allowRepeatedUploads", - "description": [ - "\nWhether this component should display a \"done\" state after processing an\nupload or return to the initial state to allow for another upload.\n" - ], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "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": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "files", - "id": "def-public.Props.onDone", - "type": "Function", - "tags": [], - "label": "onDone", - "description": [ - "\nCalled when the an upload process fully completes" - ], - "signature": [ - "(files: UploadedFile[]) => void" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.Props.onDone.$1", - "type": "Array", - "tags": [], - "label": "files", - "description": [], - "signature": [ - "UploadedFile[]" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "files", - "id": "def-public.Props.onError", - "type": "Function", - "tags": [], - "label": "onError", - "description": [ - "\nCalled when an error occurs during upload" - ], - "signature": [ - "((e: Error) => void) | undefined" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "files", - "id": "def-public.Props.onError.$1", - "type": "Object", - "tags": [], - "label": "e", - "description": [], - "signature": [ - "Error" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "files", - "id": "def-public.Props.onUploadStart", - "type": "Function", - "tags": [], - "label": "onUploadStart", - "description": [ - "\nWill be called whenever an upload starts" - ], - "signature": [ - "(() => void) | undefined" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "files", - "id": "def-public.Props.onUploadEnd", - "type": "Function", - "tags": [], - "label": "onUploadEnd", - "description": [ - "\nWill be called when attempt ends, in error otherwise" - ], - "signature": [ - "(() => void) | undefined" - ], - "path": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "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": "src/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": "src/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "files", "id": "def-public.Props", @@ -1253,7 +969,15 @@ "\nWill be called after a user has a selected a set of files" ], "signature": [ - "(fileIds: string[]) => void" + "(files: ", + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "[]) => void" ], "path": "src/plugins/files/public/components/file_picker/file_picker.tsx", "deprecated": false, @@ -1264,10 +988,17 @@ "id": "def-public.Props.onDone.$1", "type": "Array", "tags": [], - "label": "fileIds", + "label": "files", "description": [], "signature": [ - "string[]" + { + "pluginId": "files", + "scope": "common", + "docId": "kibFilesPluginApi", + "section": "def-common.FileJSON", + "text": "FileJSON" + }, + "[]" ], "path": "src/plugins/files/public/components/file_picker/file_picker.tsx", "deprecated": false, @@ -1284,12 +1015,12 @@ "tags": [], "label": "onUpload", "description": [ - "\nWhen a user has succesfully uploaded some files this callback will be called" + "\nWhen a user has successfully uploaded some files this callback will be called" ], "signature": [ "((done: ", "DoneNotification", - "[]) => void) | undefined" + "[]) => void) | undefined" ], "path": "src/plugins/files/public/components/file_picker/file_picker.tsx", "deprecated": false, @@ -1304,7 +1035,7 @@ "description": [], "signature": [ "DoneNotification", - "[]" + "[]" ], "path": "src/plugins/files/public/components/file_picker/file_picker.tsx", "deprecated": false, @@ -1537,6 +1268,28 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.UploadFileProps", + "type": "Type", + "tags": [], + "label": "UploadFileProps", + "description": [], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + " & { lazyLoadFallback?: React.ReactNode; }" + ], + "path": "src/plugins/files/public/components/upload_file/index.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [], diff --git a/api_docs/files.mdx b/api_docs/files.mdx index f993888be73bf5..1d0c78839f7d41 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-11-15 +date: 2022-11-17 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 | |-------------------|-----------|------------------------|-----------------| -| 278 | 0 | 19 | 3 | +| 263 | 0 | 18 | 3 | ## Client diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 52cea44fd29871..5074de04a47889 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-11-15 +date: 2022-11-17 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 8dbf3419706617..dd2b9e910a1499 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 2399cdc3be929d..bae65c2f28affa 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 24bcd9d9eed2f3..962b47f9f25310 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-11-15 +date: 2022-11-17 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 00ed5db492872c..b3bb06a95288ce 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-11-15 +date: 2022-11-17 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 dd9e2918b82b25..170f036cc29e01 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-11-15 +date: 2022-11-17 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 3bd123d3fa6131..eb607098528a39 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-11-15 +date: 2022-11-17 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 4e1cf1ea5653ef..75795af9032c2b 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-11-15 +date: 2022-11-17 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 46cf1a0d04fe38..c858a391f5d4bd 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-11-15 +date: 2022-11-17 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 e371379c6083df..09ae481c1f53a7 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-11-15 +date: 2022-11-17 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 bd114899ad5d05..38161abb3b4056 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-11-15 +date: 2022-11-17 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 3c4bc2a52b3872..9770ac6950966b 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-11-15 +date: 2022-11-17 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 169ead73e5162a..176c395118782e 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-11-15 +date: 2022-11-17 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 338cdeb28f01ed..58b548e40dfdbd 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.devdocs.json b/api_docs/kbn_analytics_client.devdocs.json index c0ae1007796da3..7f8a662675d11d 100644 --- a/api_docs/kbn_analytics_client.devdocs.json +++ b/api_docs/kbn_analytics_client.devdocs.json @@ -687,17 +687,13 @@ "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" }, { - "plugin": "core", - "path": "src/core/server/server.ts" + "plugin": "@kbn/core-root-server-internal", + "path": "packages/core/root/core-root-server-internal/src/server.ts" }, { "plugin": "security", "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" - }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/types.ts" @@ -706,6 +702,10 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/fleet_usage_sender.ts" + }, { "plugin": "osquery", "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" @@ -1406,6 +1406,24 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/analytics-client", + "id": "def-common.IAnalyticsClient.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nForces all shippers to send all their enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "@kbn/analytics-client", "id": "def-common.IAnalyticsClient.shutdown", @@ -1413,10 +1431,10 @@ "tags": [], "label": "shutdown", "description": [ - "\nStops the client." + "\nStops the client. Flushing any pending events in the process." ], "signature": [ - "() => void" + "() => Promise" ], "path": "packages/analytics/client/src/analytics_client/types.ts", "deprecated": false, @@ -1602,6 +1620,24 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/analytics-client", + "id": "def-common.IShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nSends all the enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "@kbn/analytics-client", "id": "def-common.IShipper.shutdown", diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 47823b1ff95b4a..7655893e5a387b 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 96 | 0 | 0 | 0 | +| 98 | 0 | 0 | 0 | ## Common diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.devdocs.json b/api_docs/kbn_analytics_shippers_elastic_v3_browser.devdocs.json index 2e1927862ade1a..ce89b234bfcae8 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.devdocs.json +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.devdocs.json @@ -288,6 +288,24 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/analytics-shippers-elastic-v3-browser", + "id": "def-common.ElasticV3BrowserShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nTriggers a flush of the internal queue to attempt to send any events held in the queue\nand resolves the returned promise once the queue is emptied." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "@kbn/analytics-shippers-elastic-v3-browser", "id": "def-common.ElasticV3BrowserShipper.shutdown", diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index af6c812e13dbe7..de8c9523b988ab 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_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 | |-------------------|-----------|------------------------|-----------------| -| 18 | 0 | 0 | 0 | +| 19 | 0 | 0 | 0 | ## Common diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 56a0068e6c9c01..bd8b5743cc3bd6 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-11-15 +date: 2022-11-17 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.devdocs.json b/api_docs/kbn_analytics_shippers_elastic_v3_server.devdocs.json index 9a0eb5bc0ac7d6..8259f7451839f6 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.devdocs.json +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.devdocs.json @@ -280,6 +280,24 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/analytics-shippers-elastic-v3-server", + "id": "def-server.ElasticV3ServerShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nTriggers a flush of the internal queue to attempt to send any events held in the queue\nand resolves the returned promise once the queue is emptied." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "@kbn/analytics-shippers-elastic-v3-server", "id": "def-server.ElasticV3ServerShipper.shutdown", diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 8ebce049520e71..1057f6d50f9fae 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_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 | |-------------------|-----------|------------------------|-----------------| -| 18 | 0 | 0 | 0 | +| 19 | 0 | 0 | 0 | ## Server diff --git a/api_docs/kbn_analytics_shippers_fullstory.devdocs.json b/api_docs/kbn_analytics_shippers_fullstory.devdocs.json index 476a7042823aca..7a55836c7d02fb 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.devdocs.json +++ b/api_docs/kbn_analytics_shippers_fullstory.devdocs.json @@ -263,6 +263,24 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/analytics-shippers-fullstory", + "id": "def-common.FullStoryShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nFlushes all internal queues of the shipper.\nIt doesn't really do anything inside because this shipper doesn't hold any internal queues." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "@kbn/analytics-shippers-fullstory", "id": "def-common.FullStoryShipper.shutdown", diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 71887b3624822a..7c2a41703ae578 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 20 | 0 | 0 | 0 | +| 21 | 0 | 0 | 0 | ## Common diff --git a/api_docs/kbn_analytics_shippers_gainsight.devdocs.json b/api_docs/kbn_analytics_shippers_gainsight.devdocs.json index 624830a0a10b58..a1ffcc4c59204a 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.devdocs.json +++ b/api_docs/kbn_analytics_shippers_gainsight.devdocs.json @@ -263,6 +263,24 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/analytics-shippers-gainsight", + "id": "def-common.GainsightShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nFlushes all internal queues of the shipper.\nIt doesn't really do anything inside because this shipper doesn't hold any internal queues." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/shippers/gainsight/src/gainsight_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "@kbn/analytics-shippers-gainsight", "id": "def-common.GainsightShipper.shutdown", diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index ecca931e475441..252b992fa3700a 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 17 | 0 | 2 | 0 | +| 18 | 0 | 2 | 0 | ## Common diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index c8724df380d185..e59699aabc70a1 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-11-15 +date: 2022-11-17 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 dd3a4d2d02fa83..4d28f60005501b 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-11-15 +date: 2022-11-17 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 0b12a671153271..c65f47ab6849cb 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-11-15 +date: 2022-11-17 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 427f88e52ed371..7ef957525f1c18 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-11-15 +date: 2022-11-17 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 faf2633501a4bc..940c73f0851fcd 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-11-15 +date: 2022-11-17 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 eb0a50865f3507..9c7c71f4e6ba2e 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-11-15 +date: 2022-11-17 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 d5f20b16135c8b..cad9b679e33a96 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-11-15 +date: 2022-11-17 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 f39fd94b812aa8..8c16689e7539e4 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-11-15 +date: 2022-11-17 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 b691914b4c9a2d..9b1c3cc3e9659a 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-11-15 +date: 2022-11-17 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 ff95b1bdcb2502..71017c0e8cb5ea 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-11-15 +date: 2022-11-17 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 fb3ed8012a48bb..a5a65e9cd7410b 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-11-15 +date: 2022-11-17 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 7b38289ba1f965..8ab5be1e79930a 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-11-15 +date: 2022-11-17 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 a093726cb9788f..93fa9807907c32 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-11-15 +date: 2022-11-17 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 ee2c2fc32664c3..3073cbdc2e40d1 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_inspector.mdx b/api_docs/kbn_content_management_inspector.mdx index d199a909055ce0..15e17ea808e27c 100644 --- a/api_docs/kbn_content_management_inspector.mdx +++ b/api_docs/kbn_content_management_inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-inspector title: "@kbn/content-management-inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-inspector plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-inspector'] --- import kbnContentManagementInspectorObj from './kbn_content_management_inspector.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 91eb588a9c54a6..ce788965c31903 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-11-15 +date: 2022-11-17 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.devdocs.json b/api_docs/kbn_core_analytics_browser.devdocs.json index b0ced1c35a7de4..1f9e8beb3c1608 100644 --- a/api_docs/kbn_core_analytics_browser.devdocs.json +++ b/api_docs/kbn_core_analytics_browser.devdocs.json @@ -19,7 +19,42 @@ "common": { "classes": [], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.KbnAnalyticsWindowApi", + "type": "Interface", + "tags": [], + "label": "KbnAnalyticsWindowApi", + "description": [ + "\nAPI exposed through `window.__kbnAnalytics`" + ], + "path": "packages/core/analytics/core-analytics-browser/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.KbnAnalyticsWindowApi.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nReturns a promise that resolves when all the events in the queue have been sent." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/core/analytics/core-analytics-browser/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [ { diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 1a18ca462c57e4..062565f729f0ad 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; @@ -21,10 +21,13 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2 | 0 | 0 | 0 | +| 4 | 0 | 0 | 0 | ## Common +### Interfaces + + ### Consts, variables and types diff --git a/api_docs/kbn_core_analytics_browser_internal.devdocs.json b/api_docs/kbn_core_analytics_browser_internal.devdocs.json index 7852bf8e4b033b..e07bfe1110f2e7 100644 --- a/api_docs/kbn_core_analytics_browser_internal.devdocs.json +++ b/api_docs/kbn_core_analytics_browser_internal.devdocs.json @@ -133,7 +133,7 @@ "label": "stop", "description": [], "signature": [ - "() => void" + "() => Promise" ], "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts", "deprecated": false, diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 80b1ee8364315e..f2702b2932807a 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-11-15 +date: 2022-11-17 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 3034151db42a89..86192d6885605b 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-11-15 +date: 2022-11-17 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 021592f599c8be..51d5f950187fbb 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-11-15 +date: 2022-11-17 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.devdocs.json b/api_docs/kbn_core_analytics_server_internal.devdocs.json index 01e3743f2f806e..e12cd7a4e45f6d 100644 --- a/api_docs/kbn_core_analytics_server_internal.devdocs.json +++ b/api_docs/kbn_core_analytics_server_internal.devdocs.json @@ -130,7 +130,7 @@ "label": "stop", "description": [], "signature": [ - "() => void" + "() => Promise" ], "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts", "deprecated": false, diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index e4d597db03fb05..866bec323ac72d 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-11-15 +date: 2022-11-17 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 f9da97a2f88f0d..ac5213a67915be 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-11-15 +date: 2022-11-17 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 a306994ce8860f..c93797e097c1c7 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-11-15 +date: 2022-11-17 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 149a2f43bb4d15..06e419df0442ae 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-11-15 +date: 2022-11-17 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 882fbfdb27cabc..9be90d6462004f 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-11-15 +date: 2022-11-17 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 fcaa49158a216d..ea34a3864413dd 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-11-15 +date: 2022-11-17 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 3d6c1f9dba2807..c668de3be12b4c 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-11-15 +date: 2022-11-17 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 91840a2cd30283..d0f6dda437736b 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index 9198c2f610997b..fcb86a60ec8f42 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 326b8e4ce37082..145df8e27a8bf8 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-11-15 +date: 2022-11-17 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 2e47c541a8ca8c..1070fb91d0a6bc 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-11-15 +date: 2022-11-17 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 2e226792eb92c2..83a5af567790e1 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-11-15 +date: 2022-11-17 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 9355e46b4fcb8e..7842660014495a 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-11-15 +date: 2022-11-17 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 3b13e7155e0925..0b899b43cc4f68 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-11-15 +date: 2022-11-17 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 ca79600cbe70fd..f6c8342ba39325 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-11-15 +date: 2022-11-17 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 b0754f12d090f0..899105a77aa9c7 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-11-15 +date: 2022-11-17 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 66920bcfe22ec0..4cb463adcdd57c 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-11-15 +date: 2022-11-17 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.mdx b/api_docs/kbn_core_chrome_browser.mdx index b16f9efeb03e6b..20115ee8b28e5f 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index d62b32559f477a..4685032e143884 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-11-15 +date: 2022-11-17 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 e800ebdcd6a9ed..a9ac87fa6af394 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-11-15 +date: 2022-11-17 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 645ea01df6af68..c690c64d8941e4 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-11-15 +date: 2022-11-17 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 8391b68a649e8c..1e7e233d5f7636 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-11-15 +date: 2022-11-17 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 8b0b18899bfd87..f756eec44ccad5 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-11-15 +date: 2022-11-17 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 bdb1771e932db0..3b3effe5bc5684 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-11-15 +date: 2022-11-17 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 9d18e86bbd82e6..ce5a3ca8c9153d 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-11-15 +date: 2022-11-17 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 ba97dab000eaf6..fadd15215af3a2 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-11-15 +date: 2022-11-17 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 5cf14b018a8c83..20fa60516b7ee6 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-11-15 +date: 2022-11-17 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 88a4617ea6438d..e091f7257df054 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-11-15 +date: 2022-11-17 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 30b1f5d199eb73..b40a716679db74 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-11-15 +date: 2022-11-17 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 db346176fd58c4..c07a52848728a2 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-11-15 +date: 2022-11-17 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 79da72b47d0729..dcf8a48100ec57 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-11-15 +date: 2022-11-17 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 9f7b7a227c4d62..ebb45627cacbd9 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-11-15 +date: 2022-11-17 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 a014bbb2ba166e..6b66243ae049cc 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-11-15 +date: 2022-11-17 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 c871b7219a2af5..1bbb0e1062a046 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-11-15 +date: 2022-11-17 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 ad52c001d9fa3e..089d2cbd3cf82e 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-11-15 +date: 2022-11-17 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 fea73633c75008..13744384a2a4e9 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-11-15 +date: 2022-11-17 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 4d44fa011b05b9..d0ce309fab2348 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-11-15 +date: 2022-11-17 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 d398d2443b9b31..1bddac22ab27ec 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-11-15 +date: 2022-11-17 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 7cb081220b5243..24fac8e625e5ca 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-11-15 +date: 2022-11-17 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 60b3afdfd9f619..86209a465666df 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-11-15 +date: 2022-11-17 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 23a10ff59402af..2c37e9d6fd5031 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-11-15 +date: 2022-11-17 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 24e6ddbbccbc6f..611c97bf401297 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-11-15 +date: 2022-11-17 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 fd75e41f35e4b8..e3d919e8913f95 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-11-15 +date: 2022-11-17 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 9a0ea560091e43..efb662eac45e83 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-11-15 +date: 2022-11-17 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 a677aae3dbe817..2053ea0bb60815 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-11-15 +date: 2022-11-17 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 7d8239af35ef2c..84c01942bfe21c 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-11-15 +date: 2022-11-17 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 0b60d81f76f70a..86daad0ed20675 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-11-15 +date: 2022-11-17 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 2c1029f520969c..0be0c484754c2e 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-11-15 +date: 2022-11-17 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 24ca177cd16677..526ae6bf5d2bcc 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-11-15 +date: 2022-11-17 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 a5d41f2b2f9d5d..a8c02421a58302 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-11-15 +date: 2022-11-17 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 f2027b3b031899..1a37bf33414022 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-11-15 +date: 2022-11-17 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 6f0a3dd0f808ae..e30d00cbccd3de 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-11-15 +date: 2022-11-17 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 3931cf5c608d6c..d043a7b7b8cba6 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-11-15 +date: 2022-11-17 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 efc968da78673d..7a3f0e597c61b2 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-11-15 +date: 2022-11-17 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 945b57424b0e59..fe97a5f3d41891 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-11-15 +date: 2022-11-17 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 cff3ead11d2c35..2dffa64ef5dc88 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-11-15 +date: 2022-11-17 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 c6c61c40b4b85d..5f661d56189a12 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-11-15 +date: 2022-11-17 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 bcfdc7b9ca7a77..d2a672611858bb 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-11-15 +date: 2022-11-17 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 4ff6bf3168a86d..6da3fa1c63a8d2 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index bbaa67443d3b0a..a5fe3b48eccd5b 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-11-15 +date: 2022-11-17 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 2e663e5e4dbd3d..16747bbbc0da78 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-11-15 +date: 2022-11-17 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 260206897dda1f..30e07115f0009b 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-11-15 +date: 2022-11-17 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 514c93817ababe..9c9a20135f954a 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-11-15 +date: 2022-11-17 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 6f99af314013ea..2d427ae59e9c22 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-11-15 +date: 2022-11-17 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 f5de18f258d423..a984578e62443b 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-11-15 +date: 2022-11-17 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 ffd42e9ef360fb..b30331b3ebd132 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-11-15 +date: 2022-11-17 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 57c436f76bee44..4b9ff78723d1f2 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-11-15 +date: 2022-11-17 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 e0edc2f97638f8..4699fe72ed84e1 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-11-15 +date: 2022-11-17 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 5d28a1f1f6b05a..402850e5738adb 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-11-15 +date: 2022-11-17 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 1f276c599f7531..66b661f0a08e00 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-11-15 +date: 2022-11-17 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 ed4647e3f14ee7..7e5fba8b4b80f2 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-11-15 +date: 2022-11-17 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 75d925ded836a9..d484b5fb16cd16 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index f9aa251c8a7689..b53d88112c2aab 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index 94999ffaa469e1..2e59594612d827 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index 14512b85e78bf3..2fcdd363a2e035 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index a5cbf55f24e3b4..312141a828d3c1 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 53f053fa4bd45f..9de9140c7a4311 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-11-15 +date: 2022-11-17 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 a51c41f5c9d852..30a868e31e8dce 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-11-15 +date: 2022-11-17 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 027a244329c9d1..b8410474cc4d3d 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-11-15 +date: 2022-11-17 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 06090277c1e1eb..8ed8eaa3f33a7b 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-11-15 +date: 2022-11-17 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 716b18271a51bf..be70b3b7913a61 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-11-15 +date: 2022-11-17 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 6134732c2a1345..32b979411dbe1b 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-11-15 +date: 2022-11-17 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 b70ba21fa082d7..cf3ce500ebe064 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-11-15 +date: 2022-11-17 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 1b6539896fac73..26721a68e20533 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-11-15 +date: 2022-11-17 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 d13d8a9d8ee476..3204bd9da3cae1 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-11-15 +date: 2022-11-17 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 10a71f3a867f7e..9f1ec84e175250 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-11-15 +date: 2022-11-17 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 16479f4d07c47d..313a2089a19867 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-11-15 +date: 2022-11-17 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 06eaa4a9be8ee7..41ab447bfc36cd 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-11-15 +date: 2022-11-17 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 d921b764a7bd20..0385a59ff88e4f 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-11-15 +date: 2022-11-17 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 08a657c0d7a385..79c56bd950e198 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-11-15 +date: 2022-11-17 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 5c4544bb4de342..3a7289c8b12f52 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-11-15 +date: 2022-11-17 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 e2998c71b65464..f9b8781a8b73b7 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-11-15 +date: 2022-11-17 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 077a98f7b67daa..2ba74fb5f6c73a 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-11-15 +date: 2022-11-17 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 e08f8ad52fa57e..bb3093cb556d8e 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-11-15 +date: 2022-11-17 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 98be907750376e..87252570e61fdc 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-11-15 +date: 2022-11-17 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 94471bd846ce13..131cfff7e534e8 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-11-15 +date: 2022-11-17 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.mdx b/api_docs/kbn_core_plugins_server.mdx index 830327ee2060b2..d988c16a378055 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index 850719ba262d2d..2ad924c3302b03 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index f910f3e2463416..23bef83b664b0e 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-11-15 +date: 2022-11-17 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 3dd9be91d7a08d..b21b676f8d0055 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-11-15 +date: 2022-11-17 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 ec74b58d591be7..5f629cf9b2e709 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-11-15 +date: 2022-11-17 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 e0ac33ca90b38e..f722715b6a6d76 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-11-15 +date: 2022-11-17 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 1fea9e6b6399ea..e7ac6fbf6a1a3c 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.devdocs.json b/api_docs/kbn_core_root_server_internal.devdocs.json new file mode 100644 index 00000000000000..8dff7846c2e91d --- /dev/null +++ b/api_docs/kbn_core_root_server_internal.devdocs.json @@ -0,0 +1,426 @@ +{ + "id": "@kbn/core-root-server-internal", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [ + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root", + "type": "Class", + "tags": [], + "label": "Root", + "description": [ + "\nTop-level entry point to kick off the app and start the Kibana server." + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root.logger", + "type": "Object", + "tags": [], + "label": "logger", + "description": [], + "signature": [ + { + "pluginId": "@kbn/logging", + "scope": "server", + "docId": "kibKbnLoggingPluginApi", + "section": "def-server.LoggerFactory", + "text": "LoggerFactory" + } + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "rawConfigProvider", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config", + "scope": "server", + "docId": "kibKbnConfigPluginApi", + "section": "def-server.RawConfigurationProvider", + "text": "RawConfigurationProvider" + } + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root.Unnamed.$2", + "type": "Object", + "tags": [], + "label": "env", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config", + "scope": "server", + "docId": "kibKbnConfigPluginApi", + "section": "def-server.Env", + "text": "Env" + } + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root.Unnamed.$3", + "type": "Function", + "tags": [], + "label": "onShutdown", + "description": [], + "signature": [ + "((reason?: string | Error | undefined) => void) | undefined" + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root.preboot", + "type": "Function", + "tags": [], + "label": "preboot", + "description": [], + "signature": [ + "() => Promise<", + "InternalCorePreboot", + ">" + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root.setup", + "type": "Function", + "tags": [], + "label": "setup", + "description": [], + "signature": [ + "() => Promise<", + "InternalCoreSetup", + ">" + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root.start", + "type": "Function", + "tags": [], + "label": "start", + "description": [], + "signature": [ + "() => Promise<", + "InternalCoreStart", + ">" + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root.shutdown", + "type": "Function", + "tags": [], + "label": "shutdown", + "description": [], + "signature": [ + "(reason?: any) => Promise" + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Root.shutdown.$1", + "type": "Any", + "tags": [], + "label": "reason", + "description": [], + "signature": [ + "any" + ], + "path": "packages/core/root/core-root-server-internal/src/root/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server", + "type": "Class", + "tags": [], + "label": "Server", + "description": [], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.configService", + "type": "Object", + "tags": [], + "label": "configService", + "description": [], + "signature": [ + "ConfigService" + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.pluginsInitialized", + "type": "CompoundType", + "tags": [], + "label": "#pluginsInitialized", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "rawConfigProvider", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config", + "scope": "server", + "docId": "kibKbnConfigPluginApi", + "section": "def-server.RawConfigurationProvider", + "text": "RawConfigurationProvider" + } + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.Unnamed.$2", + "type": "Object", + "tags": [], + "label": "env", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config", + "scope": "server", + "docId": "kibKbnConfigPluginApi", + "section": "def-server.Env", + "text": "Env" + } + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.Unnamed.$3", + "type": "Object", + "tags": [], + "label": "loggingSystem", + "description": [], + "signature": [ + "ILoggingSystem" + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.preboot", + "type": "Function", + "tags": [], + "label": "preboot", + "description": [], + "signature": [ + "() => Promise<", + "InternalCorePreboot", + ">" + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.setup", + "type": "Function", + "tags": [], + "label": "setup", + "description": [], + "signature": [ + "() => Promise<", + "InternalCoreSetup", + ">" + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.start", + "type": "Function", + "tags": [], + "label": "start", + "description": [], + "signature": [ + "() => Promise<", + "InternalCoreStart", + ">" + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.stop", + "type": "Function", + "tags": [], + "label": "stop", + "description": [], + "signature": [ + "() => Promise" + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-root-server-internal", + "id": "def-server.Server.setupCoreConfig", + "type": "Function", + "tags": [], + "label": "setupCoreConfig", + "description": [], + "signature": [ + "() => void" + ], + "path": "packages/core/root/core-root-server-internal/src/server.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx new file mode 100644 index 00000000000000..fe38ff36d74d0c --- /dev/null +++ b/api_docs/kbn_core_root_server_internal.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: kibKbnCoreRootServerInternalPluginApi +slug: /kibana-dev-docs/api/kbn-core-root-server-internal +title: "@kbn/core-root-server-internal" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/core-root-server-internal plugin +date: 2022-11-17 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] +--- +import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 23 | 1 | 22 | 0 | + +## Server + +### Classes + + diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 6f80c1eff962a5..903922b168243e 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 25323964f9dbab..3003009d581e06 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-11-15 +date: 2022-11-17 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 da5715646c24df..f79ca8d1898bae 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-11-15 +date: 2022-11-17 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 60a83b644e2c0f..2d4acc808f6b6c 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-11-15 +date: 2022-11-17 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 26f3b707d86eb8..ff1db6d53d7ea2 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-11-15 +date: 2022-11-17 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 61a4ee4ce62edc..546cd50bfc7a86 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-11-15 +date: 2022-11-17 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 a3b3416d2846d3..19fa1d8d636379 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-11-15 +date: 2022-11-17 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 c31a5eb11e0b85..e771a66f606315 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-11-15 +date: 2022-11-17 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 084233b1249f7f..714fe3ca5cd6ad 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-11-15 +date: 2022-11-17 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 813a8e4e76d845..0b0cc315137032 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -340,22 +340,6 @@ "plugin": "embeddable", "path": "src/plugins/embeddable/public/types.ts" }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/epm.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/epm.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/settings.ts" - }, - { - "plugin": "fleet", - "path": "x-pack/plugins/fleet/common/types/models/settings.ts" - }, { "plugin": "visualizations", "path": "src/plugins/visualizations/public/utils/saved_objects_utils/save_with_confirmation.ts" @@ -436,6 +420,22 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/dashboard_saved_object/lib/load_dashboard_state_from_saved_object.ts" }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/epm.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/epm.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/settings.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/common/types/models/settings.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx" @@ -704,10 +704,6 @@ "plugin": "alerting", "path": "x-pack/plugins/alerting/server/types.ts" }, - { - "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" - }, { "plugin": "alerting", "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts" @@ -816,14 +812,6 @@ "plugin": "core", "path": "src/core/types/index.ts" }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, - { - "plugin": "ml", - "path": "x-pack/plugins/ml/common/types/modules.ts" - }, { "plugin": "visualizations", "path": "src/plugins/visualizations/common/types.ts" @@ -864,6 +852,14 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/common/persistable_state/dashboard_saved_object_references.ts" }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, + { + "plugin": "ml", + "path": "x-pack/plugins/ml/common/types/modules.ts" + }, { "plugin": "core", "path": "src/core/server/types.ts" diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 93d3abc89e2ec9..2e22ac78092b49 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 7025e705311704..69a889b4fd9fb7 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-11-15 +date: 2022-11-17 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 859aca01f4dc72..8f4cbf660bc5b4 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-11-15 +date: 2022-11-17 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 b32669f0c34047..1b94ab75206a72 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-11-15 +date: 2022-11-17 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 e700fab4ac1719..b7fe92a8771fee 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 5c05ae919064e1..3ace0850238285 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index cf27e39d0bbeea..85d24a3beab12e 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-11-15 +date: 2022-11-17 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 38665686483d39..42d8cc447c93ff 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-11-15 +date: 2022-11-17 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 08dca8dfddd80c..7f65d3efd2c4f0 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-11-15 +date: 2022-11-17 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 8556eab16c9bbc..67ab063b9d7ecc 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-11-15 +date: 2022-11-17 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 ffaa723524e7ce..eb6bcf0fb8cc43 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-11-15 +date: 2022-11-17 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 14d27a3ecf1d45..4585d0ed052481 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-11-15 +date: 2022-11-17 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 f31c78cddc8a8d..938098f1d5e017 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-11-15 +date: 2022-11-17 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 748cc0c1e06d9d..89742770e0f5cf 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-11-15 +date: 2022-11-17 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 827b3e2dc07d59..93cd3e4f85d868 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-11-15 +date: 2022-11-17 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 1be443835725aa..48180e68e793c3 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-11-15 +date: 2022-11-17 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 9c501b111ff6b7..3dde0c3ad41a91 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-11-15 +date: 2022-11-17 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 b7aca3a0f84a57..eff5424c88eb73 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-11-15 +date: 2022-11-17 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 bdac11a2314493..3bccf6252af92a 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-11-15 +date: 2022-11-17 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 9c13fe143e580d..aea3f73233db27 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-11-15 +date: 2022-11-17 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 dadee518c34932..53ca097a6b2be7 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-11-15 +date: 2022-11-17 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 52b9f7bb5a2969..ffcf86d74bef5e 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-11-15 +date: 2022-11-17 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 592e13dab8cd62..0031eabf7d29c0 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-11-15 +date: 2022-11-17 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 d7f5bf230a3c05..a64217aff405ad 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-11-15 +date: 2022-11-17 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 c8338e1d385fcf..41b4cee5b2b85f 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-11-15 +date: 2022-11-17 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 3684e26236900d..7f419e90a404f3 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-11-15 +date: 2022-11-17 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 6f676d6365f9e2..6759ec9d4d0d16 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-11-15 +date: 2022-11-17 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 e3503599f872f1..63b49605babf3b 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-11-15 +date: 2022-11-17 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 999169677a7a0f..c3264ab950639a 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-11-15 +date: 2022-11-17 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 ef18ad95e93ccf..87b70692aae1d2 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-11-15 +date: 2022-11-17 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 e32f9e4dff9e25..d31e95455de920 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-11-15 +date: 2022-11-17 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 8b537e68c58b0f..3e363267f6a8df 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-11-15 +date: 2022-11-17 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 60826fb35177c0..91612423d97070 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-11-15 +date: 2022-11-17 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 3666d84a9892d1..8fd83ee2492a56 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-11-15 +date: 2022-11-17 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 7dbfcc173d2819..29faf402e97cf2 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-11-15 +date: 2022-11-17 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 6526d827961291..98465d44b7f866 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-11-15 +date: 2022-11-17 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 310c67b5528d5b..08c36b54078b15 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-11-15 +date: 2022-11-17 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 d4b45849cae6b9..6c695219b8273c 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-11-15 +date: 2022-11-17 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 59846f75e673fe..50c2d3b0fed7a7 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -546,7 +546,7 @@ "label": "securitySolution", "description": [], "signature": [ - "{ readonly trustedApps: string; readonly eventFilters: string; readonly blocklist: string; readonly endpointArtifacts: string; readonly policyResponseTroubleshooting: { full_disk_access: string; macos_system_ext: string; linux_deadlock: string; }; readonly packageActionTroubleshooting: { es_connection: string; }; readonly threatIntelInt: string; readonly responseActions: string; readonly configureEndpointIntegrationPolicy: string; readonly exceptions: { value_lists: string; }; }" + "{ readonly trustedApps: string; readonly eventFilters: string; readonly blocklist: string; readonly endpointArtifacts: string; readonly policyResponseTroubleshooting: { full_disk_access: string; macos_system_ext: string; linux_deadlock: string; }; readonly packageActionTroubleshooting: { es_connection: string; }; readonly threatIntelInt: string; readonly responseActions: string; readonly configureEndpointIntegrationPolicy: string; readonly exceptions: { value_lists: string; }; readonly privileges: 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 4f03feb5023ab8..e6f4520a7af2fa 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-11-15 +date: 2022-11-17 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 23df16db144c3b..dcb4a3db01bdf6 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-11-15 +date: 2022-11-17 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 3bada57b27b465..fb7a12df900c3e 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index 62a61081226ee2..94810796a56da9 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 68afa212fc7016..c6e29fa674d0c1 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-11-15 +date: 2022-11-17 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 58154a74e1ba7b..d5378d7d037853 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-11-15 +date: 2022-11-17 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 5be762cb15a515..823eb0a7d69ead 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-11-15 +date: 2022-11-17 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 18b24b7afe3d41..c3d48d15415412 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-11-15 +date: 2022-11-17 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 d1b8920d30bc43..9959c175abc513 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-11-15 +date: 2022-11-17 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 0c09d56e0d863c..b49e9a1d68e20f 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-11-15 +date: 2022-11-17 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 4060b6f4185db4..ba6412a42b1fb2 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-11-15 +date: 2022-11-17 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 a7b39f97ca5740..2793921c796a27 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-11-15 +date: 2022-11-17 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 c5a22e34b88ab8..959997f6373fd1 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-11-15 +date: 2022-11-17 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 9b7a2f65f640af..70a165d7dfb6a6 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-11-15 +date: 2022-11-17 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.mdx b/api_docs/kbn_guided_onboarding.mdx index bdec839c63fc0c..3d6c15344db48c 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 33d581b6b32d9d..46bee503a59c1d 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-11-15 +date: 2022-11-17 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 55ed7dde39cc02..3f6b6a4baf8fd6 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index 89f5e5c0e08e46..70ed54a483679a 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 2c44f793632f5e..1ff30e5fd4bced 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-11-15 +date: 2022-11-17 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 b721a20f6155b6..6841153c8097d8 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-11-15 +date: 2022-11-17 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 959056a931bc69..e476eda27ab484 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index 9bd07ae7ceb1ca..6fe073b46c4c70 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 8472723b82cab9..7d8d9cda7da4ef 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-11-15 +date: 2022-11-17 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 794d0f6119edcd..19d69e7a9750d7 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-11-15 +date: 2022-11-17 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 770e89253cdaa9..da449f8b59ff9c 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-11-15 +date: 2022-11-17 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 a0834fda9c049d..c45b7911574337 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-11-15 +date: 2022-11-17 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 bbebfccd36e4f9..7d501dd18eaf0a 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-11-15 +date: 2022-11-17 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 0e52c94b1a97f5..f7d79e631dc492 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-11-15 +date: 2022-11-17 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.mdx b/api_docs/kbn_language_documentation_popover.mdx index 8e3c1842b55e13..459bd08caa2359 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 852a3f17833b65..90efd4b0a86bd6 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-11-15 +date: 2022-11-17 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 a70d9b4d11c3e9..c09fa9df428416 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-11-15 +date: 2022-11-17 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 c429db6293cf1b..b1a43d05ab4cd8 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-11-15 +date: 2022-11-17 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 dbcfe068bd4459..8d1c611fbe8ada 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-11-15 +date: 2022-11-17 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 007fc573b49400..98945a37a3b4bf 100644 --- a/api_docs/kbn_ml_agg_utils.devdocs.json +++ b/api_docs/kbn_ml_agg_utils.devdocs.json @@ -440,6 +440,39 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.getSampleProbability", + "type": "Function", + "tags": [], + "label": "getSampleProbability", + "description": [], + "signature": [ + "(totalDocCount: number) => number" + ], + "path": "x-pack/packages/ml/agg_utils/src/get_sample_probability.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.getSampleProbability.$1", + "type": "number", + "tags": [], + "label": "totalDocCount", + "description": [], + "signature": [ + "number" + ], + "path": "x-pack/packages/ml/agg_utils/src/get_sample_probability.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/ml-agg-utils", "id": "def-server.getSamplerAggregationsResponsePath", @@ -1349,6 +1382,21 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.RANDOM_SAMPLER_SEED", + "type": "number", + "tags": [], + "label": "RANDOM_SAMPLER_SEED", + "description": [], + "signature": [ + "3867412" + ], + "path": "x-pack/packages/ml/agg_utils/src/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [] diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 0009ca71c10735..58bc9b94b531e6 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-11-15 +date: 2022-11-17 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 | |-------------------|-----------|------------------------|-----------------| -| 79 | 2 | 55 | 0 | +| 82 | 2 | 58 | 0 | ## Server diff --git a/api_docs/kbn_ml_is_populated_object.devdocs.json b/api_docs/kbn_ml_is_populated_object.devdocs.json index ed63d11aac6968..6eabb3128c34da 100644 --- a/api_docs/kbn_ml_is_populated_object.devdocs.json +++ b/api_docs/kbn_ml_is_populated_object.devdocs.json @@ -21,7 +21,7 @@ "\nA type guard to check record like object structures.\n\nExamples:\n- `isPopulatedObject({...})`\n Limits type to Record\n\n- `isPopulatedObject({...}, ['attribute'])`\n Limits type to Record<'attribute', unknown>\n\n- `isPopulatedObject({...})`\n Limits type to a record with keys of the given interface.\n Note that you might want to add keys from the interface to the\n array of requiredAttributes to satisfy runtime requirements.\n Otherwise you'd just satisfy TS requirements but might still\n run into runtime issues." ], "signature": [ - "(arg: unknown, requiredAttributes?: U[]) => arg is Record" + "(arg: unknown, requiredAttributes?: U[]) => arg is Record" ], "path": "x-pack/packages/ml/is_populated_object/src/is_populated_object.ts", "deprecated": false, diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index a4791494ac4e06..0c34416cae8cd7 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-11-15 +date: 2022-11-17 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 a3b504eca02bf7..d1fed13cc4f16b 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-11-15 +date: 2022-11-17 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 38697c001c26b5..fae0f3ea5858b9 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-11-15 +date: 2022-11-17 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 555242b1a4a4e8..719ae39b4667be 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-11-15 +date: 2022-11-17 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 27872727897861..13c2bffd85cc5c 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-11-15 +date: 2022-11-17 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 17f6631bd21e14..f023ad5e53bc8b 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-11-15 +date: 2022-11-17 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 183c3c7340088b..cb30a880c9d5bd 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-11-15 +date: 2022-11-17 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 1bb8c0b839898d..6e5e61be34b890 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-11-15 +date: 2022-11-17 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 06d16e852ab7ca..be837367237a39 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-11-15 +date: 2022-11-17 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 0102cdfb7cf63b..770dee65dff986 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-11-15 +date: 2022-11-17 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 530ecfe512c2d8..3a4304ed84385d 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-11-15 +date: 2022-11-17 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 3deae016848888..a8bbf2df0d1fd5 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -59,6 +59,39 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.getRuleDetailsRoute", + "type": "Function", + "tags": [], + "label": "getRuleDetailsRoute", + "description": [], + "signature": [ + "(ruleId: string) => string" + ], + "path": "packages/kbn-rule-data-utils/src/routes/stack_rule_paths.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.getRuleDetailsRoute.$1", + "type": "string", + "tags": [], + "label": "ruleId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-rule-data-utils/src/routes/stack_rule_paths.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/rule-data-utils", "id": "def-common.getSafeSortIds", @@ -850,6 +883,96 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_SUPPRESSION_DOCS_COUNT", + "type": "string", + "tags": [], + "label": "ALERT_SUPPRESSION_DOCS_COUNT", + "description": [], + "signature": [ + "\"kibana.alert.suppression.docs_count\"" + ], + "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_SUPPRESSION_END", + "type": "string", + "tags": [], + "label": "ALERT_SUPPRESSION_END", + "description": [], + "signature": [ + "\"kibana.alert.suppression.end\"" + ], + "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_SUPPRESSION_FIELD", + "type": "string", + "tags": [], + "label": "ALERT_SUPPRESSION_FIELD", + "description": [], + "signature": [ + "\"kibana.alert.suppression.terms.field\"" + ], + "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_SUPPRESSION_START", + "type": "string", + "tags": [], + "label": "ALERT_SUPPRESSION_START", + "description": [], + "signature": [ + "\"kibana.alert.suppression.start\"" + ], + "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_SUPPRESSION_TERMS", + "type": "string", + "tags": [], + "label": "ALERT_SUPPRESSION_TERMS", + "description": [], + "signature": [ + "\"kibana.alert.suppression.terms\"" + ], + "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_SUPPRESSION_VALUE", + "type": "string", + "tags": [], + "label": "ALERT_SUPPRESSION_VALUE", + "description": [], + "signature": [ + "\"kibana.alert.suppression.terms.value\"" + ], + "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/rule-data-utils", "id": "def-common.ALERT_SYSTEM_STATUS", @@ -1210,6 +1333,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ruleDetailsRoute", + "type": "string", + "tags": [], + "label": "ruleDetailsRoute", + "description": [], + "signature": [ + "\"/rule/:ruleId\"" + ], + "path": "packages/kbn-rule-data-utils/src/routes/stack_rule_paths.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/rule-data-utils", "id": "def-common.SPACE_IDS", @@ -1263,7 +1401,7 @@ "label": "TechnicalRuleDataFieldName", "description": [], "signature": [ - "\"tags\" | \"kibana\" | \"@timestamp\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"event.action\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.rule\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.space_ids\" | \"kibana.alert.uuid\" | \"kibana.alert.instance.id\" | \"kibana.alert.start\" | \"kibana.alert.time_range\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity\" | \"kibana.alert.status\" | \"kibana.alert.flapping\" | \"kibana.version\" | \"ecs.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.workflow_status\" | \"kibana.alert.workflow_user\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.system_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.author\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.created_at\" | \"kibana.alert.rule.created_by\" | \"kibana.alert.rule.description\" | \"kibana.alert.rule.enabled\" | \"kibana.alert.rule.from\" | \"kibana.alert.rule.interval\" | \"kibana.alert.rule.license\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"event.kind\" | \"event.module\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.building_block_type\" | \"kibana.alert.rule.exceptions_list\" | \"kibana.alert.rule.namespace\" | \"kibana.alert.rule.threat.framework\" | \"kibana.alert.rule.threat.tactic.id\" | \"kibana.alert.rule.threat.tactic.name\" | \"kibana.alert.rule.threat.tactic.reference\" | \"kibana.alert.rule.threat.technique.id\" | \"kibana.alert.rule.threat.technique.name\" | \"kibana.alert.rule.threat.technique.reference\" | \"kibana.alert.rule.threat.technique.subtechnique.id\" | \"kibana.alert.rule.threat.technique.subtechnique.name\" | \"kibana.alert.rule.threat.technique.subtechnique.reference\"" + "\"tags\" | \"kibana\" | \"@timestamp\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"event.action\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.rule\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.space_ids\" | \"kibana.alert.uuid\" | \"kibana.alert.instance.id\" | \"kibana.alert.start\" | \"kibana.alert.time_range\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity\" | \"kibana.alert.status\" | \"kibana.alert.flapping\" | \"kibana.version\" | \"ecs.version\" | \"kibana.alert.risk_score\" | \"kibana.alert.workflow_status\" | \"kibana.alert.workflow_user\" | \"kibana.alert.workflow_reason\" | \"kibana.alert.system_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.author\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.created_at\" | \"kibana.alert.rule.created_by\" | \"kibana.alert.rule.description\" | \"kibana.alert.rule.enabled\" | \"kibana.alert.rule.from\" | \"kibana.alert.rule.interval\" | \"kibana.alert.rule.license\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.note\" | \"kibana.alert.rule.references\" | \"kibana.alert.rule.rule_id\" | \"kibana.alert.rule.rule_name_override\" | \"kibana.alert.rule.tags\" | \"kibana.alert.rule.to\" | \"kibana.alert.rule.type\" | \"kibana.alert.rule.updated_at\" | \"kibana.alert.rule.updated_by\" | \"kibana.alert.rule.version\" | \"kibana.alert.suppression.terms\" | \"kibana.alert.suppression.terms.field\" | \"kibana.alert.suppression.terms.value\" | \"kibana.alert.suppression.start\" | \"kibana.alert.suppression.end\" | \"kibana.alert.suppression.docs_count\" | \"event.kind\" | \"event.module\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.building_block_type\" | \"kibana.alert.rule.exceptions_list\" | \"kibana.alert.rule.namespace\" | \"kibana.alert.rule.threat.framework\" | \"kibana.alert.rule.threat.tactic.id\" | \"kibana.alert.rule.threat.tactic.name\" | \"kibana.alert.rule.threat.tactic.reference\" | \"kibana.alert.rule.threat.technique.id\" | \"kibana.alert.rule.threat.technique.name\" | \"kibana.alert.rule.threat.technique.reference\" | \"kibana.alert.rule.threat.technique.subtechnique.id\" | \"kibana.alert.rule.threat.technique.subtechnique.name\" | \"kibana.alert.rule.threat.technique.subtechnique.reference\"" ], "path": "packages/kbn-rule-data-utils/src/technical_field_names.ts", "deprecated": false, @@ -1285,6 +1423,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.triggersActionsRoute", + "type": "string", + "tags": [], + "label": "triggersActionsRoute", + "description": [], + "signature": [ + "\"/app/management/insightsAndAlerting/triggersActions\"" + ], + "path": "packages/kbn-rule-data-utils/src/routes/stack_rule_paths.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/rule-data-utils", "id": "def-common.ValidFeatureId", diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 67bd54674623db..48ffb91be1319a 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 86 | 0 | 83 | 0 | +| 96 | 0 | 93 | 0 | ## Common diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 53d36b0b36666e..e4f1d9abe3a185 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-11-15 +date: 2022-11-17 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 316ed6303b6829..81efaf0dccb2a0 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-11-15 +date: 2022-11-17 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.devdocs.json b/api_docs/kbn_securitysolution_exception_list_components.devdocs.json index 4b0660f89c540d..884f4ef0080856 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.devdocs.json +++ b/api_docs/kbn_securitysolution_exception_list_components.devdocs.json @@ -544,6 +544,88 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.BackOptions", + "type": "Interface", + "tags": [], + "label": "BackOptions", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/list_header/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.BackOptions.pageId", + "type": "string", + "tags": [], + "label": "pageId", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/list_header/index.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.BackOptions.path", + "type": "string", + "tags": [], + "label": "path", + "description": [], + "path": "packages/kbn-securitysolution-exception-list-components/src/list_header/index.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.BackOptions.dataTestSubj", + "type": "string", + "tags": [], + "label": "dataTestSubj", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/list_header/index.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.BackOptions.onNavigate", + "type": "Function", + "tags": [], + "label": "onNavigate", + "description": [], + "signature": [ + "(path: string) => void" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/list_header/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-exception-list-components", + "id": "def-common.BackOptions.onNavigate.$1", + "type": "string", + "tags": [], + "label": "path", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-securitysolution-exception-list-components/src/list_header/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/securitysolution-exception-list-components", "id": "def-common.ExceptionItemCardCommentsProps", @@ -1343,10 +1425,10 @@ }, { "parentPluginId": "@kbn/securitysolution-exception-list-components", - "id": "def-common.Rule.exception_list", + "id": "def-common.Rule.exceptions_list", "type": "Array", "tags": [], - "label": "exception_list", + "label": "exceptions_list", "description": [], "signature": [ "{ id: string; list_id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; namespace_type: \"single\" | \"agnostic\"; }[] | undefined" diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 88f0358db53d4d..a7c37c9e05b8ac 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 96 | 0 | 85 | 1 | +| 102 | 0 | 91 | 1 | ## Common diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index a98382642240ca..633f7bc9c9b962 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 08b5d8aeaae117..e7a04508a0af0e 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.devdocs.json b/api_docs/kbn_securitysolution_io_ts_list_types.devdocs.json index 0a9c5e43f0363e..6e3b4956953d4a 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.devdocs.json +++ b/api_docs/kbn_securitysolution_io_ts_list_types.devdocs.json @@ -470,6 +470,27 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-list-types", + "id": "def-common.ApiCallFetchExceptionListsProps.sort", + "type": "Object", + "tags": [], + "label": "sort", + "description": [], + "signature": [ + { + "pluginId": "@kbn/securitysolution-io-ts-list-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsListTypesPluginApi", + "section": "def-common.Sort", + "text": "Sort" + }, + " | undefined" + ], + "path": "packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/securitysolution-io-ts-list-types", "id": "def-common.ApiCallFetchExceptionListsProps.filters", @@ -1757,6 +1778,42 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-list-types", + "id": "def-common.Sort", + "type": "Interface", + "tags": [], + "label": "Sort", + "description": [], + "path": "packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-io-ts-list-types", + "id": "def-common.Sort.field", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "path": "packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-list-types", + "id": "def-common.Sort.order", + "type": "string", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/securitysolution-io-ts-list-types", "id": "def-common.UpdateExceptionListItemProps", @@ -2115,6 +2172,27 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-list-types", + "id": "def-common.UseExceptionListProps.sort", + "type": "Object", + "tags": [], + "label": "sort", + "description": [], + "signature": [ + { + "pluginId": "@kbn/securitysolution-io-ts-list-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsListTypesPluginApi", + "section": "def-common.Sort", + "text": "Sort" + }, + " | undefined" + ], + "path": "packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -2238,6 +2316,27 @@ "path": "packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-list-types", + "id": "def-common.UseExceptionListsProps.initialSort", + "type": "Object", + "tags": [], + "label": "initialSort", + "description": [], + "signature": [ + { + "pluginId": "@kbn/securitysolution-io-ts-list-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsListTypesPluginApi", + "section": "def-common.Sort", + "text": "Sort" + }, + " | undefined" + ], + "path": "packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 16a23d7b3e4ce6..acebb4182e77e7 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_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 | |-------------------|-----------|------------------------|-----------------| -| 505 | 1 | 492 | 0 | +| 511 | 1 | 498 | 0 | ## Common diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index 152e84349149b9..dc2ae8f1e15007 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-11-15 +date: 2022-11-17 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 bd8e176120b92d..cfcee7e7f05942 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-11-15 +date: 2022-11-17 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.devdocs.json b/api_docs/kbn_securitysolution_list_api.devdocs.json index 4d4136d67c60d0..cd8334e742dda8 100644 --- a/api_docs/kbn_securitysolution_list_api.devdocs.json +++ b/api_docs/kbn_securitysolution_list_api.devdocs.json @@ -595,7 +595,7 @@ "label": "fetchExceptionListsWithValidation", "description": [], "signature": [ - "({ filters, http, namespaceTypes, pagination, signal, }: ", + "({ filters, http, namespaceTypes, pagination, signal, sort, }: ", { "pluginId": "@kbn/securitysolution-io-ts-list-types", "scope": "common", @@ -614,7 +614,7 @@ "id": "def-common.fetchExceptionListsWithValidation.$1", "type": "Object", "tags": [], - "label": "{\n filters,\n http,\n namespaceTypes,\n pagination,\n signal,\n}", + "label": "{\n filters,\n http,\n namespaceTypes,\n pagination,\n signal,\n sort,\n}", "description": [], "signature": [ { diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 86eacb230997ec..2109c2452185fa 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-11-15 +date: 2022-11-17 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 887c3fde332e89..7e1ef0bca3478c 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-11-15 +date: 2022-11-17 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.devdocs.json b/api_docs/kbn_securitysolution_list_hooks.devdocs.json index 67eade2f7b66f3..3282b2dfbadd48 100644 --- a/api_docs/kbn_securitysolution_list_hooks.devdocs.json +++ b/api_docs/kbn_securitysolution_list_hooks.devdocs.json @@ -387,7 +387,7 @@ "\nHook for fetching ExceptionLists\n" ], "signature": [ - "({ errorMessage, http, initialPagination, filterOptions, namespaceTypes, notifications, hideLists, }: ", + "({ errorMessage, http, initialPagination, filterOptions, namespaceTypes, notifications, hideLists, initialSort, }: ", { "pluginId": "@kbn/securitysolution-io-ts-list-types", "scope": "common", @@ -413,7 +413,7 @@ "id": "def-common.useExceptionLists.$1", "type": "Object", "tags": [], - "label": "{\n errorMessage,\n http,\n initialPagination = DEFAULT_PAGINATION,\n filterOptions = {},\n namespaceTypes,\n notifications,\n hideLists = [],\n}", + "label": "{\n errorMessage,\n http,\n initialPagination = DEFAULT_PAGINATION,\n filterOptions = {},\n namespaceTypes,\n notifications,\n hideLists = [],\n initialSort = DEFAULT_SORT,\n}", "description": [], "signature": [ { @@ -1353,7 +1353,23 @@ "section": "def-common.Func", "text": "Func" }, - " | null]" + " | null, sort: ", + { + "pluginId": "@kbn/securitysolution-io-ts-list-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsListTypesPluginApi", + "section": "def-common.Sort", + "text": "Sort" + }, + ", setSort: React.Dispatch>]" ], "path": "packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts", "deprecated": false, diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index 100d4f6358f428..c8577f775f1643 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-11-15 +date: 2022-11-17 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 8cd1856c58b847..87e026610f5051 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-11-15 +date: 2022-11-17 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 c52fd4384ee852..80a0723b1af6b6 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-11-15 +date: 2022-11-17 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 a861809783d4a5..3ffe888e1db9fb 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-11-15 +date: 2022-11-17 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 ad21768895ac4d..33acdd7b9d352e 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-11-15 +date: 2022-11-17 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 fd05a89b0a3d6c..65b8fcaee2e785 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-11-15 +date: 2022-11-17 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 ce63b5c3044187..bab2e1c7af40ce 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-11-15 +date: 2022-11-17 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 56564290d670ab..4eecc315e8476f 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index 6d45ffd6833cd1..e1a0a2f8ff6bda 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 538ac53a714693..6ea0f42746bb44 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-11-15 +date: 2022-11-17 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.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index 4cdf187c9b97c4..07f0dcfdeab859 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index b05d5daae819d8..a2ae7e127b0dc1 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-11-15 +date: 2022-11-17 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 5957a0d4eef25b..794cb2a7521c5b 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-11-15 +date: 2022-11-17 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 f87fec89c11253..7f59580b891e12 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-11-15 +date: 2022-11-17 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 7cee906113399d..88fbd1f27377d1 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-11-15 +date: 2022-11-17 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.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index ae6608a7a7f6ae..9dff8f073ce554 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 2073acb2e8c797..3da056d5baf190 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-11-15 +date: 2022-11-17 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 ec03f331f8cea1..e1fbb95ca482c6 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-11-15 +date: 2022-11-17 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 a0176dc741e7a6..c2cb3338f30aeb 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-11-15 +date: 2022-11-17 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 a79c63f491464f..0d83bd48557987 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-11-15 +date: 2022-11-17 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 a43a5fc47ddb3e..16847e487b12b1 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-11-15 +date: 2022-11-17 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 d745aa5def7fb5..5b46aa267f8ac1 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-11-15 +date: 2022-11-17 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 5398485a7a7390..dd15142536e2cf 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-11-15 +date: 2022-11-17 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 f4b25d9624c6ba..6ca8e0be798193 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-11-15 +date: 2022-11-17 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 d02f42feead441..0653d0d0e87617 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-11-15 +date: 2022-11-17 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 b53245e184c95f..4f3497b0138b95 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-11-15 +date: 2022-11-17 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 f5eb6d71a6d4c7..890659d31bbab8 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-11-15 +date: 2022-11-17 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 5533ca8b9d8723..eade29ceeaed2a 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-11-15 +date: 2022-11-17 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 233f40076c8a8b..772c8259aec02a 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-11-15 +date: 2022-11-17 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 383176e6817773..2cc4c323c3166e 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-11-15 +date: 2022-11-17 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 93c1bb9a098328..bf95a589b530f2 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-11-15 +date: 2022-11-17 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 e74913881777c1..eaaebcf1aa7ac3 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-11-15 +date: 2022-11-17 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 5f358055cb2f7f..e415071f185fd8 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-11-15 +date: 2022-11-17 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 a434788cc68d08..5153b488265284 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-11-15 +date: 2022-11-17 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 59ea5ecfd17544..45e840b91afae5 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-11-15 +date: 2022-11-17 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 3f5b5fa154a062..69c2c14ca101b8 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-11-15 +date: 2022-11-17 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 ae0aa8e37b7691..e2d49b1ddb40d4 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-11-15 +date: 2022-11-17 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 bd1af52f31a1dc..f6f42c6757d9bb 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-11-15 +date: 2022-11-17 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 27431c65582a1c..4f825cd655945d 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-11-15 +date: 2022-11-17 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 188721b09a6ed6..7cb0df660d7f9c 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-11-15 +date: 2022-11-17 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 b13de4a9bc408a..af5b3ab80fa290 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-11-15 +date: 2022-11-17 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 fd8d8f26c09b43..f3797a34e392c0 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-11-15 +date: 2022-11-17 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 91d81ffdf47a1a..7a0d9e0bfc867f 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-11-15 +date: 2022-11-17 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 64e8dcc18d3d49..0549cce0d49c50 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-11-15 +date: 2022-11-17 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 883abd50b1fbfd..d66f0d86e41ae8 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-11-15 +date: 2022-11-17 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 cf7c5ef660aefd..2b09a050921c3a 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-11-15 +date: 2022-11-17 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 314a884b56b238..08a35774b1713a 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-11-15 +date: 2022-11-17 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 1f7082bbd90e02..a537411737addc 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-11-15 +date: 2022-11-17 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 172619dacbc5f7..baf48eec4f2ff1 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-11-15 +date: 2022-11-17 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 af72483ba33182..1b5b7a6c08eb57 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-11-15 +date: 2022-11-17 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_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index a80b63bbb023c1..87d4df0ed1ca44 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 4c58e2386d38cf..171f91d6281fb6 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-11-15 +date: 2022-11-17 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 a7fce86ff7664f..47b5e5d465f080 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-11-15 +date: 2022-11-17 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 8ae1d10084e788..df473b54dcdc28 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-11-15 +date: 2022-11-17 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 fb73f0afd1598d..a5319befe9f120 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-11-15 +date: 2022-11-17 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 c7b23b6cc5f9cc..906d1bfae133b7 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-11-15 +date: 2022-11-17 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 ab60c57a3a277a..1e70ec71d7105a 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-11-15 +date: 2022-11-17 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 4bd8243c7322b9..711af9895ec4ae 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.devdocs.json b/api_docs/kibana_react.devdocs.json index c48961d89d73a6..542b9509d3c462 100644 --- a/api_docs/kibana_react.devdocs.json +++ b/api_docs/kibana_react.devdocs.json @@ -3625,20 +3625,7 @@ "path": "src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx", "deprecated": true, "trackAdoption": false, - "references": [ - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx" - }, - { - "plugin": "dashboard", - "path": "src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx" - } - ], + "references": [], "initialIsOpen": false }, { diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index a897b433125e88..329c522bb7c80f 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-11-15 +date: 2022-11-17 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 01bce25eca59d5..964cba8aadadcc 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-11-15 +date: 2022-11-17 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 42d9a2edb2f34e..9424efd6a2143f 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index f581582c7c99a9..fccf2f8a0eed42 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 2ba0788fa7996e..bdf0bf9bf99194 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-11-15 +date: 2022-11-17 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 5dbc68bdd71e63..07690d8820e411 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 3b223dfc8b40e2..4fec68fbce9e29 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.devdocs.json b/api_docs/lists.devdocs.json index ea176fe9e1bf9e..50291bc74fdb97 100644 --- a/api_docs/lists.devdocs.json +++ b/api_docs/lists.devdocs.json @@ -726,6 +726,44 @@ "The exception list item created, otherwise null if not created" ] }, + { + "parentPluginId": "lists", + "id": "def-server.ExceptionListClient.duplicateExceptionListAndItems", + "type": "Function", + "tags": [], + "label": "duplicateExceptionListAndItems", + "description": [ + "\nCreate the Trusted Apps Agnostic list if it does not yet exist (`null` is returned if it does exist)" + ], + "signature": [ + "({ listId, namespaceType, }: ", + "DuplicateExceptionListOptions", + ") => Promise<{ _version: string | undefined; created_at: string; created_by: string; description: string; id: string; immutable: boolean; list_id: string; meta: object | undefined; name: string; namespace_type: \"single\" | \"agnostic\"; os_types: (\"windows\" | \"linux\" | \"macos\")[]; tags: string[]; tie_breaker_id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; updated_at: string; updated_by: string; version: number; } | null>" + ], + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "lists", + "id": "def-server.ExceptionListClient.duplicateExceptionListAndItems.$1", + "type": "Object", + "tags": [], + "label": "{\n listId,\n namespaceType,\n }", + "description": [], + "signature": [ + "DuplicateExceptionListOptions" + ], + "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "The exception list schema or null if it does not exist" + ] + }, { "parentPluginId": "lists", "id": "def-server.ExceptionListClient.updateEndpointListItem", diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 55ce2617607f94..2975c91344f29c 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Security detections response](https://github.com/orgs/elastic/teams/sec | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 204 | 0 | 92 | 50 | +| 206 | 0 | 93 | 51 | ## Client diff --git a/api_docs/management.mdx b/api_docs/management.mdx index dfb0abe618ff97..e3ecc65c75c852 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 6ec80f478a5151..d44392aec42435 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 3d637e56b4151a..ec9e8fe855ed40 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-11-15 +date: 2022-11-17 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 4e23f09f052bdf..ad6573695819db 100644 --- a/api_docs/ml.devdocs.json +++ b/api_docs/ml.devdocs.json @@ -1609,7 +1609,7 @@ "label": "ML_PAGES", "description": [], "signature": [ - "{ readonly ANOMALY_DETECTION_JOBS_MANAGE: \"jobs\"; readonly ANOMALY_EXPLORER: \"explorer\"; readonly SINGLE_METRIC_VIEWER: \"timeseriesexplorer\"; readonly DATA_FRAME_ANALYTICS_JOBS_MANAGE: \"data_frame_analytics\"; readonly DATA_FRAME_ANALYTICS_SOURCE_SELECTION: \"data_frame_analytics/source_selection\"; readonly DATA_FRAME_ANALYTICS_CREATE_JOB: \"data_frame_analytics/new_job\"; readonly TRAINED_MODELS_MANAGE: \"trained_models\"; readonly TRAINED_MODELS_NODES: \"trained_models/nodes\"; readonly DATA_FRAME_ANALYTICS_EXPLORATION: \"data_frame_analytics/exploration\"; readonly DATA_FRAME_ANALYTICS_MAP: \"data_frame_analytics/map\"; readonly DATA_VISUALIZER: \"datavisualizer\"; readonly DATA_VISUALIZER_INDEX_SELECT: \"datavisualizer_index_select\"; readonly DATA_VISUALIZER_FILE: \"filedatavisualizer\"; readonly DATA_VISUALIZER_INDEX_VIEWER: \"jobs/new_job/datavisualizer\"; readonly ANOMALY_DETECTION_CREATE_JOB: \"jobs/new_job\"; readonly ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER: \"jobs/new_job/recognize\"; readonly ANOMALY_DETECTION_CREATE_JOB_SINGLE_METRIC: \"jobs/new_job/single_metric\"; readonly ANOMALY_DETECTION_CREATE_JOB_MULTI_METRIC: \"jobs/new_job/multi_metric\"; readonly ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_MULTI_METRIC: \"jobs/new_job/convert_to_multi_metric\"; readonly ANOMALY_DETECTION_CREATE_JOB_ADVANCED: \"jobs/new_job/advanced\"; readonly ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_ADVANCED: \"jobs/new_job/convert_to_advanced\"; readonly ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: \"jobs/new_job/step/job_type\"; readonly ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX: \"jobs/new_job/step/index_or_search\"; readonly ANOMALY_DETECTION_CREATE_JOB_FROM_LENS: \"jobs/new_job/from_lens\"; readonly SETTINGS: \"settings\"; readonly CALENDARS_MANAGE: \"settings/calendars_list\"; readonly CALENDARS_NEW: \"settings/calendars_list/new_calendar\"; readonly CALENDARS_EDIT: \"settings/calendars_list/edit_calendar\"; readonly FILTER_LISTS_MANAGE: \"settings/filter_lists\"; readonly FILTER_LISTS_NEW: \"settings/filter_lists/new_filter_list\"; readonly FILTER_LISTS_EDIT: \"settings/filter_lists/edit_filter_list\"; readonly ACCESS_DENIED: \"access-denied\"; readonly OVERVIEW: \"overview\"; readonly NOTIFICATIONS: \"notifications\"; readonly AIOPS: \"aiops\"; readonly AIOPS_EXPLAIN_LOG_RATE_SPIKES: \"aiops/explain_log_rate_spikes\"; readonly AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT: \"aiops/explain_log_rate_spikes_index_select\"; readonly AIOPS_LOG_CATEGORIZATION: \"aiops/log_categorization\"; readonly AIOPS_LOG_CATEGORIZATION_INDEX_SELECT: \"aiops/log_categorization_index_select\"; }" + "{ readonly ANOMALY_DETECTION_JOBS_MANAGE: \"jobs\"; readonly ANOMALY_EXPLORER: \"explorer\"; readonly SINGLE_METRIC_VIEWER: \"timeseriesexplorer\"; readonly DATA_FRAME_ANALYTICS_JOBS_MANAGE: \"data_frame_analytics\"; readonly DATA_FRAME_ANALYTICS_SOURCE_SELECTION: \"data_frame_analytics/source_selection\"; readonly DATA_FRAME_ANALYTICS_CREATE_JOB: \"data_frame_analytics/new_job\"; readonly TRAINED_MODELS_MANAGE: \"trained_models\"; readonly TRAINED_MODELS_NODES: \"trained_models/nodes\"; readonly DATA_FRAME_ANALYTICS_EXPLORATION: \"data_frame_analytics/exploration\"; readonly DATA_FRAME_ANALYTICS_MAP: \"data_frame_analytics/map\"; readonly DATA_VISUALIZER: \"datavisualizer\"; readonly DATA_VISUALIZER_INDEX_SELECT: \"datavisualizer_index_select\"; readonly DATA_VISUALIZER_FILE: \"filedatavisualizer\"; readonly DATA_VISUALIZER_INDEX_VIEWER: \"jobs/new_job/datavisualizer\"; readonly ANOMALY_DETECTION_CREATE_JOB: \"jobs/new_job\"; readonly ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER: \"jobs/new_job/recognize\"; readonly ANOMALY_DETECTION_CREATE_JOB_SINGLE_METRIC: \"jobs/new_job/single_metric\"; readonly ANOMALY_DETECTION_CREATE_JOB_MULTI_METRIC: \"jobs/new_job/multi_metric\"; readonly ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_MULTI_METRIC: \"jobs/new_job/convert_to_multi_metric\"; readonly ANOMALY_DETECTION_CREATE_JOB_ADVANCED: \"jobs/new_job/advanced\"; readonly ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_ADVANCED: \"jobs/new_job/convert_to_advanced\"; readonly ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: \"jobs/new_job/step/job_type\"; readonly ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX: \"jobs/new_job/step/index_or_search\"; readonly ANOMALY_DETECTION_CREATE_JOB_FROM_LENS: \"jobs/new_job/from_lens\"; readonly SETTINGS: \"settings\"; readonly CALENDARS_MANAGE: \"settings/calendars_list\"; readonly CALENDARS_NEW: \"settings/calendars_list/new_calendar\"; readonly CALENDARS_EDIT: \"settings/calendars_list/edit_calendar\"; readonly FILTER_LISTS_MANAGE: \"settings/filter_lists\"; readonly FILTER_LISTS_NEW: \"settings/filter_lists/new_filter_list\"; readonly FILTER_LISTS_EDIT: \"settings/filter_lists/edit_filter_list\"; readonly ACCESS_DENIED: \"access-denied\"; readonly OVERVIEW: \"overview\"; readonly NOTIFICATIONS: \"notifications\"; readonly AIOPS: \"aiops\"; readonly AIOPS_EXPLAIN_LOG_RATE_SPIKES: \"aiops/explain_log_rate_spikes\"; readonly AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT: \"aiops/explain_log_rate_spikes_index_select\"; readonly AIOPS_LOG_CATEGORIZATION: \"aiops/log_categorization\"; readonly AIOPS_LOG_CATEGORIZATION_INDEX_SELECT: \"aiops/log_categorization_index_select\"; readonly AIOPS_CHANGE_POINT_DETECTION: \"aiops/change_point_detection\"; readonly AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT: \"aiops/change_point_detection_index_select\"; }" ], "path": "x-pack/plugins/ml/common/constants/locator.ts", "deprecated": false, diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 7f9a741419db9e..bcc3424bd50b58 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index a35a49df238540..1e10064fac939f 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-11-15 +date: 2022-11-17 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 c1859102da832f..dbca2b7c0ba3a3 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-11-15 +date: 2022-11-17 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 fc071fd94928a2..290559a5f12592 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-11-15 +date: 2022-11-17 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 18cdb1a5b2aa0d..11a755903bfba6 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index 44066209bfabc3..69e43cef179cae 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2022-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index d869c6c6d13de1..cf3480924f3ac0 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -3839,7 +3839,7 @@ "label": "format", "description": [], "signature": [ - "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -3858,7 +3858,7 @@ "label": "options", "description": [], "signature": [ - "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -5396,7 +5396,7 @@ "label": "ObservabilityRuleTypeFormatter", "description": [], "signature": [ - "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "(options: { fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", @@ -5415,7 +5415,7 @@ "label": "options", "description": [], "signature": [ - "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", + "{ fields: OutputOf> & Record; formatters: { asDuration: (value: ", "Maybe", ", { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: ", "Maybe", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 9def8a31f48b14..0d1c81fb782938 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-11-15 +date: 2022-11-17 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 4b7df010df8c3c..3eedd5db5db3ca 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-11-15 +date: 2022-11-17 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 d8fbd90585a28c..2f78e8f71305ed 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 510 | 428 | 38 | +| 511 | 429 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 33396 | 517 | 23707 | 1119 | +| 33452 | 518 | 23758 | 1126 | ## Plugin Directory @@ -29,9 +29,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] |--------------|----------------|-----------|--------------|----------|---------------|--------| | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 226 | 8 | 221 | 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) | - | 415 | 0 | 406 | 27 | -| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 41 | 0 | 41 | 58 | +| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 12 | 0 | 1 | 2 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 417 | 0 | 408 | 27 | +| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 41 | 0 | 41 | 57 | | | [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 | @@ -46,7 +46,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [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 | 237 | 0 | 228 | 7 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2704 | 17 | 1202 | 0 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2708 | 17 | 1202 | 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 | @@ -84,7 +84,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [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 | 26 | 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. | 278 | 0 | 19 | 3 | +| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 263 | 0 | 18 | 3 | | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1020 | 3 | 915 | 18 | | | [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 | @@ -109,7 +109,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [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 | -| | [Security detections response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 204 | 0 | 92 | 50 | +| | [Security detections response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 206 | 0 | 93 | 51 | | logstash | [Logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 41 | 0 | 41 | 6 | | | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 266 | 0 | 265 | 26 | @@ -123,7 +123,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 567 | 42 | 564 | 31 | | | [Security asset management](https://github.com/orgs/elastic/teams/security-asset-management) | - | 21 | 0 | 21 | 4 | | painlessLab | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | -| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 243 | 8 | 187 | 12 | +| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 242 | 8 | 186 | 12 | | | [profiling](https://github.com/orgs/elastic/teams/profiling-ui) | - | 14 | 2 | 14 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 4 | 0 | 4 | 0 | | | [Kibana Reporting Services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Reporting Services enables applications to feature reports that the user can automate with Watcher and download later. | 36 | 0 | 16 | 0 | @@ -139,7 +139,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 32 | 0 | 13 | 0 | | | [Kibana Reporting Services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 27 | 0 | 8 | 4 | | searchprofiler | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | -| | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 250 | 0 | 90 | 0 | +| | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 250 | 0 | 90 | 1 | | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 112 | 0 | 75 | 26 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 7 | 0 | 7 | 1 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds URL Service and sharing capabilities to Kibana | 115 | 0 | 56 | 10 | @@ -157,10 +157,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 462 | 1 | 350 | 33 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [Kibana Localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 530 | 11 | 501 | 51 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 531 | 11 | 502 | 51 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 133 | 2 | 92 | 11 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 206 | 0 | 142 | 9 | -| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 192 | 0 | 187 | 4 | +| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 196 | 0 | 188 | 7 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | The `unifiedHistogram` plugin provides UI components to create a layout including a resizable histogram and a main display. | 56 | 0 | 29 | 0 | | | [Visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 134 | 2 | 106 | 18 | | upgradeAssistant | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | @@ -193,12 +193,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Machine Learning UI | Static utilities for AIOps related efforts. | 53 | 0 | 22 | 0 | | | [Owner missing] | Alerts components and hooks | 9 | 1 | 9 | 0 | | | Kibana Core | Kibana Analytics tool | 73 | 0 | 73 | 2 | -| | Kibana Core | - | 96 | 0 | 0 | 0 | -| | Kibana Core | - | 18 | 0 | 0 | 0 | +| | Kibana Core | - | 98 | 0 | 0 | 0 | +| | Kibana Core | - | 19 | 0 | 0 | 0 | | | Kibana Core | - | 23 | 0 | 0 | 0 | -| | Kibana Core | - | 18 | 0 | 0 | 0 | -| | Kibana Core | - | 20 | 0 | 0 | 0 | -| | Kibana Core | - | 17 | 0 | 2 | 0 | +| | Kibana Core | - | 19 | 0 | 0 | 0 | +| | Kibana Core | - | 21 | 0 | 0 | 0 | +| | Kibana Core | - | 18 | 0 | 2 | 0 | | | [Owner missing] | - | 17 | 0 | 17 | 0 | | | [Owner missing] | Elastic APM trace data generator | 76 | 0 | 76 | 13 | | | [Owner missing] | - | 11 | 0 | 11 | 0 | @@ -215,7 +215,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 129 | 3 | 127 | 17 | | | [Owner missing] | - | 6 | 0 | 4 | 3 | | | [Owner missing] | - | 20 | 0 | 13 | 5 | -| | Kibana Core | - | 2 | 0 | 0 | 0 | +| | Kibana Core | - | 4 | 0 | 0 | 0 | | | Kibana Core | - | 7 | 0 | 7 | 1 | | | Kibana Core | - | 4 | 0 | 4 | 0 | | | Kibana Core | - | 3 | 0 | 0 | 0 | @@ -322,6 +322,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 2 | 0 | 2 | 0 | | | Kibana Core | - | 2 | 0 | 2 | 1 | | | Kibana Core | - | 4 | 0 | 4 | 1 | +| | Kibana Core | - | 23 | 1 | 22 | 0 | | | Kibana Core | - | 107 | 1 | 76 | 0 | | | Kibana Core | - | 310 | 1 | 137 | 0 | | | Kibana Core | - | 71 | 0 | 51 | 1 | @@ -402,7 +403,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | 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. | 79 | 2 | 55 | 0 | +| | Machine Learning UI | This package includes utility functions related to creating elasticsearch aggregation queries, data manipulation and verification. | 82 | 2 | 58 | 0 | | | 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 | @@ -414,13 +415,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | Just some helpers for kibana plugin devs. | 1 | 0 | 1 | 0 | | | [Owner missing] | - | 21 | 0 | 10 | 0 | | | [Owner missing] | - | 6 | 0 | 6 | 1 | -| | [Owner missing] | - | 86 | 0 | 83 | 0 | +| | [Owner missing] | - | 96 | 0 | 93 | 0 | | | [Owner missing] | Security Solution auto complete | 56 | 1 | 41 | 1 | | | [Owner missing] | security solution elastic search utilities to use across plugins such lists, security_solution, cases, etc... | 67 | 0 | 61 | 1 | -| | [Owner missing] | - | 96 | 0 | 85 | 1 | +| | [Owner missing] | - | 102 | 0 | 91 | 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 | 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 | 511 | 1 | 498 | 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 | | | [Owner missing] | security solution list REST API | 67 | 0 | 64 | 0 | diff --git a/api_docs/presentation_util.devdocs.json b/api_docs/presentation_util.devdocs.json index 7c02ccf9c73eeb..d1a5f094f45424 100644 --- a/api_docs/presentation_util.devdocs.json +++ b/api_docs/presentation_util.devdocs.json @@ -746,30 +746,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "presentationUtil", - "id": "def-public.getStubPluginServices", - "type": "Function", - "tags": [], - "label": "getStubPluginServices", - "description": [], - "signature": [ - "() => ", - { - "pluginId": "presentationUtil", - "scope": "public", - "docId": "kibPresentationUtilPluginApi", - "section": "def-public.PresentationUtilPluginStart", - "text": "PresentationUtilPluginStart" - } - ], - "path": "src/plugins/presentation_util/public/services/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "presentationUtil", "id": "def-public.isValidDataUrl", @@ -2112,7 +2088,7 @@ "tags": [], "label": "PresentationCapabilitiesService", "description": [], - "path": "src/plugins/presentation_util/public/services/capabilities.ts", + "path": "src/plugins/presentation_util/public/services/capabilities/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2126,7 +2102,7 @@ "signature": [ "() => boolean" ], - "path": "src/plugins/presentation_util/public/services/capabilities.ts", + "path": "src/plugins/presentation_util/public/services/capabilities/types.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2142,7 +2118,7 @@ "signature": [ "() => boolean" ], - "path": "src/plugins/presentation_util/public/services/capabilities.ts", + "path": "src/plugins/presentation_util/public/services/capabilities/types.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2158,7 +2134,7 @@ "signature": [ "() => boolean" ], - "path": "src/plugins/presentation_util/public/services/capabilities.ts", + "path": "src/plugins/presentation_util/public/services/capabilities/types.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2174,7 +2150,7 @@ "signature": [ "() => boolean" ], - "path": "src/plugins/presentation_util/public/services/capabilities.ts", + "path": "src/plugins/presentation_util/public/services/capabilities/types.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2190,7 +2166,7 @@ "tags": [], "label": "PresentationDashboardsService", "description": [], - "path": "src/plugins/presentation_util/public/services/dashboards.ts", + "path": "src/plugins/presentation_util/public/services/dashboards/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2214,7 +2190,7 @@ "PartialDashboardAttributes", ">[]>" ], - "path": "src/plugins/presentation_util/public/services/dashboards.ts", + "path": "src/plugins/presentation_util/public/services/dashboards/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2228,7 +2204,7 @@ "signature": [ "string" ], - "path": "src/plugins/presentation_util/public/services/dashboards.ts", + "path": "src/plugins/presentation_util/public/services/dashboards/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2243,7 +2219,7 @@ "signature": [ "string[]" ], - "path": "src/plugins/presentation_util/public/services/dashboards.ts", + "path": "src/plugins/presentation_util/public/services/dashboards/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2271,7 +2247,7 @@ "PartialDashboardAttributes", ">[]>" ], - "path": "src/plugins/presentation_util/public/services/dashboards.ts", + "path": "src/plugins/presentation_util/public/services/dashboards/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2285,7 +2261,7 @@ "signature": [ "string" ], - "path": "src/plugins/presentation_util/public/services/dashboards.ts", + "path": "src/plugins/presentation_util/public/services/dashboards/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2303,7 +2279,7 @@ "tags": [], "label": "PresentationLabsService", "description": [], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2317,7 +2293,7 @@ "signature": [ "(id: \"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\") => boolean" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2331,7 +2307,7 @@ "signature": [ "\"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\"" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2349,7 +2325,7 @@ "signature": [ "() => readonly [\"labs:dashboard:deferBelowFold\", \"labs:dashboard:dashboardControls\", \"labs:canvas:byValueEmbeddable\"]" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2372,7 +2348,7 @@ "text": "Project" } ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2386,7 +2362,7 @@ "signature": [ "\"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\"" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2412,7 +2388,7 @@ }, ">" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2426,7 +2402,7 @@ "signature": [ "(\"dashboard\" | \"canvas\" | \"presentation\")[] | undefined" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -2444,7 +2420,7 @@ "signature": [ "(id: \"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\", env: \"kibana\" | \"browser\" | \"session\", status: boolean) => void" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2458,7 +2434,7 @@ "signature": [ "\"labs:dashboard:deferBelowFold\" | \"labs:dashboard:dashboardControls\" | \"labs:canvas:byValueEmbeddable\"" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2473,7 +2449,7 @@ "signature": [ "\"kibana\" | \"browser\" | \"session\"" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2488,7 +2464,7 @@ "signature": [ "boolean" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2506,7 +2482,7 @@ "signature": [ "() => void" ], - "path": "src/plugins/presentation_util/public/services/labs.ts", + "path": "src/plugins/presentation_util/public/services/labs/types.ts", "deprecated": false, "trackAdoption": false, "children": [], diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index bca458841e97ff..6556afd417cc6d 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.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 | |-------------------|-----------|------------------------|-----------------| -| 243 | 8 | 187 | 12 | +| 242 | 8 | 186 | 12 | ## Client diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index df1ce0f82841f0..c59831e65bd8f3 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-11-15 +date: 2022-11-17 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 fc82c45ca68291..82c7c76427b477 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-11-15 +date: 2022-11-17 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 794436cc975e37..564230cf4acece 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-11-15 +date: 2022-11-17 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 8e8f5973908a66..c02074a60d2689 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.devdocs.json b/api_docs/rule_registry.devdocs.json index 2a600480640eb0..ad2e887fe4838c 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -63,7 +63,7 @@ "label": "get", "description": [], "signature": [ - "({ id, index }: GetAlertParams) => Promise> | undefined>" + "({ id, index }: GetAlertParams) => Promise> | undefined>" ], "path": "x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts", "deprecated": false, @@ -203,7 +203,7 @@ "SortOptions", "[] | undefined; search_after?: (string | number)[] | undefined; }) => Promise<", "SearchResponse", - ">, Record>, Record>>" ], @@ -1416,7 +1416,7 @@ "section": "def-server.GetSummarizedAlertsFnOpts", "text": "GetSummarizedAlertsFnOpts" }, - ") => Promise<{ new: { count: number; alerts: Partial> & OutputOf>>[]; }; ongoing: { count: number; alerts: Partial> & OutputOf>>[]; }; recovered: { count: number; alerts: Partial> & OutputOf>>[]; }; }>" + ") => Promise<{ new: { count: number; alerts: Partial> & OutputOf>>[]; }; ongoing: { count: number; alerts: Partial> & OutputOf>>[]; }; recovered: { count: number; alerts: Partial> & OutputOf>>[]; }; }>" ], "path": "x-pack/plugins/rule_registry/server/utils/create_get_summarized_alerts_fn.ts", "deprecated": false, @@ -1761,7 +1761,7 @@ "section": "def-server.GetSummarizedAlertsFnOpts", "text": "GetSummarizedAlertsFnOpts" }, - ") => Promise<{ new: { count: number; alerts: Partial> & OutputOf>>[]; }; ongoing: { count: number; alerts: Partial> & OutputOf>>[]; }; recovered: { count: number; alerts: Partial> & OutputOf>>[]; }; }>; name: string; validate?: { params?: ", + ") => Promise<{ new: { count: number; alerts: Partial> & OutputOf>>[]; }; ongoing: { count: number; alerts: Partial> & OutputOf>>[]; }; recovered: { count: number; alerts: Partial> & OutputOf>>[]; }; }>; name: string; validate?: { params?: ", "RuleTypeParamsValidator", " | undefined; } | undefined; id: string; cancelAlertsOnRuleTimeout?: boolean | undefined; actionGroups: ", { @@ -2448,7 +2448,7 @@ "section": "def-server.ESSearchResponse", "text": "ESSearchResponse" }, - "> & OutputOf>>, TSearchRequest, { restTotalHitsAsInt: false; }>>" + "> & OutputOf>>, TSearchRequest, { restTotalHitsAsInt: false; }>>" ], "path": "x-pack/plugins/rule_registry/server/rule_data_client/types.ts", "deprecated": false, @@ -4193,7 +4193,7 @@ "label": "parseTechnicalFields", "description": [], "signature": [ - "(input: unknown, partial?: boolean) => OutputOf>" + "(input: unknown, partial?: boolean) => OutputOf>" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, @@ -4520,7 +4520,7 @@ "label": "ParsedTechnicalFields", "description": [], "signature": [ - "{ readonly '@timestamp': string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.space_ids\": string[]; readonly \"kibana.alert.uuid\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.rule.name\": string; readonly tags?: string[] | undefined; readonly 'event.action'?: string | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.time_range\"?: unknown; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.alert.flapping\"?: number | boolean | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly 'event.kind'?: string | undefined; }" + "{ readonly '@timestamp': string; readonly \"kibana.alert.rule.rule_type_id\": string; readonly \"kibana.alert.rule.consumer\": string; readonly \"kibana.alert.rule.producer\": string; readonly \"kibana.space_ids\": string[]; readonly \"kibana.alert.uuid\": string; readonly \"kibana.alert.instance.id\": string; readonly \"kibana.alert.status\": string; readonly \"kibana.alert.rule.category\": string; readonly \"kibana.alert.rule.uuid\": string; readonly \"kibana.alert.rule.name\": string; readonly tags?: string[] | undefined; readonly 'event.action'?: string | undefined; readonly \"kibana.alert.rule.execution.uuid\"?: string | undefined; readonly \"kibana.alert.rule.parameters\"?: { [key: string]: unknown; } | undefined; readonly \"kibana.alert.start\"?: string | undefined; readonly \"kibana.alert.time_range\"?: unknown; readonly \"kibana.alert.end\"?: string | undefined; readonly \"kibana.alert.duration.us\"?: number | undefined; readonly \"kibana.alert.severity\"?: string | undefined; readonly \"kibana.alert.flapping\"?: number | boolean | undefined; readonly \"kibana.version\"?: string | undefined; readonly \"ecs.version\"?: string | undefined; readonly \"kibana.alert.risk_score\"?: number | undefined; readonly \"kibana.alert.workflow_status\"?: string | undefined; readonly \"kibana.alert.workflow_user\"?: string | undefined; readonly \"kibana.alert.workflow_reason\"?: string | undefined; readonly \"kibana.alert.system_status\"?: string | undefined; readonly \"kibana.alert.action_group\"?: string | undefined; readonly \"kibana.alert.reason\"?: string | undefined; readonly \"kibana.alert.rule.author\"?: string | undefined; readonly \"kibana.alert.rule.created_at\"?: string | undefined; readonly \"kibana.alert.rule.created_by\"?: string | undefined; readonly \"kibana.alert.rule.description\"?: string | undefined; readonly \"kibana.alert.rule.enabled\"?: string | undefined; readonly \"kibana.alert.rule.from\"?: string | undefined; readonly \"kibana.alert.rule.interval\"?: string | undefined; readonly \"kibana.alert.rule.license\"?: string | undefined; readonly \"kibana.alert.rule.note\"?: string | undefined; readonly \"kibana.alert.rule.references\"?: string[] | undefined; readonly \"kibana.alert.rule.rule_id\"?: string | undefined; readonly \"kibana.alert.rule.rule_name_override\"?: string | undefined; readonly \"kibana.alert.rule.tags\"?: string[] | undefined; readonly \"kibana.alert.rule.to\"?: string | undefined; readonly \"kibana.alert.rule.type\"?: string | undefined; readonly \"kibana.alert.rule.updated_at\"?: string | undefined; readonly \"kibana.alert.rule.updated_by\"?: string | undefined; readonly \"kibana.alert.rule.version\"?: string | undefined; readonly \"kibana.alert.suppression.terms.field\"?: string[] | undefined; readonly \"kibana.alert.suppression.terms.value\"?: string[] | undefined; readonly \"kibana.alert.suppression.start\"?: string | undefined; readonly \"kibana.alert.suppression.end\"?: string | undefined; readonly \"kibana.alert.suppression.docs_count\"?: number | undefined; readonly 'event.kind'?: string | undefined; }" ], "path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts", "deprecated": false, diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 9ce702a38240e7..e109ed5e4d2923 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-11-15 +date: 2022-11-17 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 93f254fa785ee2..06db4d41eb53ff 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-11-15 +date: 2022-11-17 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 fd079df9d0332d..5c9c7860818e40 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-11-15 +date: 2022-11-17 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 4d3db42d2959e1..0b186ffadd1571 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-11-15 +date: 2022-11-17 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 180914e3e145fb..1675003df67521 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-11-15 +date: 2022-11-17 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 ac155061381cc2..54015f219b0829 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-11-15 +date: 2022-11-17 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 5fd7d04345ae43..f2a051b7671ae3 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-11-15 +date: 2022-11-17 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 1ec9d09a7bebcd..18029824f8182e 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-11-15 +date: 2022-11-17 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 36a666cbb6ef12..7eb3f18ac70960 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-11-15 +date: 2022-11-17 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 e5e0d88a7bd9c8..236df7049d83c6 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-11-15 +date: 2022-11-17 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 cb96880b175efb..c6e1ad81901419 100644 --- a/api_docs/security.devdocs.json +++ b/api_docs/security.devdocs.json @@ -1597,7 +1597,9 @@ "label": "apiKeys", "description": [], "signature": [ - "{ create: (request: ", + "{ validate: (apiKeyPrams: ", + "ValidateAPIKeyParams", + ") => Promise; create: (request: ", { "pluginId": "@kbn/core-http-server", "scope": "server", diff --git a/api_docs/security.mdx b/api_docs/security.mdx index a0b9bf9d69a8d4..774148e0da83f9 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Platform Security](https://github.com/orgs/elastic/teams/kibana-securit | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 250 | 0 | 90 | 0 | +| 250 | 0 | 90 | 1 | ## Client diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 49b03d166a1478..961dd71a56f200 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-11-15 +date: 2022-11-17 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 7ac2147b85cde5..eb59f546d51e19 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-11-15 +date: 2022-11-17 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 8a33ceafa4f2cc..5aee943618d194 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-11-15 +date: 2022-11-17 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 e30edf33df59e4..adbbed8269b70e 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-11-15 +date: 2022-11-17 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 71e244c15c41de..e20ca0b85380e7 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-11-15 +date: 2022-11-17 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 564a683059230d..1504f87a7927bf 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-11-15 +date: 2022-11-17 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 6899f5644f04a4..849d6e3eb3f8a9 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 2838c161fb042a..0781dcb18634b1 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-11-15 +date: 2022-11-17 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 3e8b61b7e2a1dc..439ee24c75902f 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-11-15 +date: 2022-11-17 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 5b46b0e4ff91ea..8c7ffc1a922d69 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-11-15 +date: 2022-11-17 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 3b89fe1e05c711..9e24afbbc1f9c8 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-11-15 +date: 2022-11-17 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 24cb838c2f1fe1..8aa1fa9b5b6e88 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-11-15 +date: 2022-11-17 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 fd5fe8068cb825..4b5a4a858f6b9f 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-11-15 +date: 2022-11-17 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 8a329334423a9f..7fe56c56463e64 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -5700,7 +5700,7 @@ "label": "value", "description": [], "signature": [ - "string | number" + "string | number | (string | number)[]" ], "path": "x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts", "deprecated": false, @@ -5728,7 +5728,7 @@ "label": "operator", "description": [], "signature": [ - "\":\" | \":*\"" + "\"includes\" | \":\" | \":*\"" ], "path": "x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts", "deprecated": false, @@ -7329,7 +7329,7 @@ "section": "def-common.RowRenderer", "text": "RowRenderer" }, - "[] | undefined; setFlyoutAlert?: ((data: any) => void) | undefined; scopeId: string; truncate?: boolean | undefined; key?: string | undefined; }" + "[] | undefined; setFlyoutAlert?: ((data: any) => void) | undefined; scopeId: string; truncate?: boolean | undefined; key?: string | undefined; closeCellPopover?: (() => void) | undefined; }" ], "path": "x-pack/plugins/timelines/common/types/timeline/cells/index.ts", "deprecated": false, @@ -7627,7 +7627,7 @@ "The operator applied to a field" ], "signature": [ - "\":\" | \":*\"" + "\"includes\" | \":\" | \":*\"" ], "path": "x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts", "deprecated": false, @@ -7761,7 +7761,7 @@ "section": "def-common.ISearchRequestParams", "text": "ISearchRequestParams" }, - " | undefined; id?: string | undefined; timerange: ", + " | undefined; id?: string | undefined; timerange?: ", { "pluginId": "timelines", "scope": "common", @@ -7769,7 +7769,7 @@ "section": "def-common.TimerangeInput", "text": "TimerangeInput" }, - "; defaultIndex: string[]; filterQuery: string | ", + " | undefined; defaultIndex: string[]; filterQuery: string | ", "ESQuery", " | undefined; factoryQueryType?: ", "TimelineEventsQueries", diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 7a7b65070d2d16..e30325af6a671e 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-11-15 +date: 2022-11-17 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 035bcd35366b06..bc8d21b8832ec6 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index da491994a1ec2d..c3cdedce63d861 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -55,7 +55,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly rulesListDatagrid: boolean; readonly internalAlertsTable: boolean; readonly ruleTagFilter: boolean; readonly ruleStatusFilter: boolean; readonly rulesDetailLogs: boolean; }" + "{ readonly rulesListDatagrid: boolean; readonly internalAlertsTable: boolean; readonly ruleTagFilter: boolean; readonly ruleStatusFilter: boolean; readonly rulesDetailLogs: boolean; readonly ruleLastRunOutcome: boolean; }" ], "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", "deprecated": false, @@ -3489,7 +3489,7 @@ "description": [], "signature": [ "BasicFields", - " & { tags?: string[] | undefined; kibana?: string[] | undefined; \"@timestamp\"?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"event.action\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.time_range\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.alert.flapping\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"event.module\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"kibana.alert.building_block_type\"?: string[] | undefined; \"kibana.alert.rule.exceptions_list\"?: string[] | undefined; \"kibana.alert.rule.namespace\"?: string[] | undefined; \"kibana.alert.rule.threat.framework\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.id\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.name\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.reference\"?: string[] | undefined; } & { [x: string]: unknown[]; }" + " & { tags?: string[] | undefined; kibana?: string[] | undefined; \"@timestamp\"?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"event.action\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.time_range\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.alert.flapping\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"kibana.alert.suppression.terms\"?: string[] | undefined; \"kibana.alert.suppression.terms.field\"?: string[] | undefined; \"kibana.alert.suppression.terms.value\"?: string[] | undefined; \"kibana.alert.suppression.start\"?: string[] | undefined; \"kibana.alert.suppression.end\"?: string[] | undefined; \"kibana.alert.suppression.docs_count\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"event.module\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"kibana.alert.building_block_type\"?: string[] | undefined; \"kibana.alert.rule.exceptions_list\"?: string[] | undefined; \"kibana.alert.rule.namespace\"?: string[] | undefined; \"kibana.alert.rule.threat.framework\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.id\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.name\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.reference\"?: string[] | undefined; } & { [x: string]: unknown[]; }" ], "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", "deprecated": false, @@ -4051,7 +4051,7 @@ "label": "throttle", "description": [], "signature": [ - "string | null" + "string | null | undefined" ], "path": "x-pack/plugins/alerting/common/alert_summary.ts", "deprecated": false, @@ -4634,6 +4634,20 @@ "path": "x-pack/plugins/alerting/common/rule.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.RuleAction.frequency", + "type": "Object", + "tags": [], + "label": "frequency", + "description": [], + "signature": [ + "{ summary: boolean; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; throttle: string | null; } | undefined" + ], + "path": "x-pack/plugins/alerting/common/rule.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -6681,7 +6695,7 @@ "label": "RulesListVisibleColumns", "description": [], "signature": [ - "\"ruleName\" | \"ruleTags\" | \"ruleExecutionStatusLastDate\" | \"ruleSnoozeNotify\" | \"ruleScheduleInterval\" | \"ruleExecutionStatusLastDuration\" | \"ruleExecutionPercentile\" | \"ruleExecutionSuccessRatio\" | \"ruleExecutionStatus\" | \"ruleExecutionState\"" + "\"ruleName\" | \"ruleTags\" | \"ruleExecutionStatusLastDate\" | \"ruleSnoozeNotify\" | \"ruleScheduleInterval\" | \"ruleExecutionStatusLastDuration\" | \"ruleExecutionPercentile\" | \"ruleExecutionSuccessRatio\" | \"ruleExecutionStatus\" | \"ruleExecutionState\" | \"ruleLastRunOutcome\"" ], "path": "x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_column_selector.tsx", "deprecated": false, @@ -9110,7 +9124,7 @@ "\nParses the string value used in `xpack.trigger_actions_ui.enableExperimental` kibana configuration,\nwhich should be a string of values delimited by a comma (`,`)\n" ], "signature": [ - "(configValue: string[]) => Readonly<{ rulesListDatagrid: boolean; internalAlertsTable: boolean; ruleTagFilter: boolean; ruleStatusFilter: boolean; rulesDetailLogs: boolean; }>" + "(configValue: string[]) => Readonly<{ rulesListDatagrid: boolean; internalAlertsTable: boolean; ruleTagFilter: boolean; ruleStatusFilter: boolean; rulesDetailLogs: boolean; ruleLastRunOutcome: boolean; }>" ], "path": "x-pack/plugins/triggers_actions_ui/common/experimental_features.ts", "deprecated": false, @@ -9288,7 +9302,7 @@ "label": "ExperimentalFeatures", "description": [], "signature": [ - "{ readonly rulesListDatagrid: boolean; readonly internalAlertsTable: boolean; readonly ruleTagFilter: boolean; readonly ruleStatusFilter: boolean; readonly rulesDetailLogs: boolean; }" + "{ readonly rulesListDatagrid: boolean; readonly internalAlertsTable: boolean; readonly ruleTagFilter: boolean; readonly ruleStatusFilter: boolean; readonly rulesDetailLogs: boolean; readonly ruleLastRunOutcome: boolean; }" ], "path": "x-pack/plugins/triggers_actions_ui/common/experimental_features.ts", "deprecated": false, @@ -9322,7 +9336,7 @@ "\nA list of allowed values that can be used in `xpack.trigger_actions_ui.enableExperimental`.\nThis object is then used to validate and parse the value entered." ], "signature": [ - "{ readonly rulesListDatagrid: boolean; readonly internalAlertsTable: boolean; readonly ruleTagFilter: boolean; readonly ruleStatusFilter: boolean; readonly rulesDetailLogs: boolean; }" + "{ readonly rulesListDatagrid: boolean; readonly internalAlertsTable: boolean; readonly ruleTagFilter: boolean; readonly ruleStatusFilter: boolean; readonly rulesDetailLogs: boolean; readonly ruleLastRunOutcome: boolean; }" ], "path": "x-pack/plugins/triggers_actions_ui/common/experimental_features.ts", "deprecated": false, diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 66e6cb18a7a1d5..ed584fd9352f99 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.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 | |-------------------|-----------|------------------------|-----------------| -| 530 | 11 | 501 | 51 | +| 531 | 11 | 502 | 51 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 14896e2e9e2b07..6c5c9334c4d597 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index a6cb7fc7df62e8..e2148de4751575 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.devdocs.json b/api_docs/unified_field_list.devdocs.json index 2434cfac2a6d7f..5a94f2bda5c938 100644 --- a/api_docs/unified_field_list.devdocs.json +++ b/api_docs/unified_field_list.devdocs.json @@ -261,6 +261,60 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldTopValuesBucket", + "type": "Function", + "tags": [], + "label": "FieldTopValuesBucket", + "description": [], + "signature": [ + "React.FunctionComponent<", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.FieldTopValuesBucketProps", + "text": "FieldTopValuesBucketProps" + }, + ">" + ], + "path": "src/plugins/unified_field_list/public/components/field_stats/index.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldTopValuesBucket.$1", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P & { children?: React.ReactNode; }" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldTopValuesBucket.$2", + "type": "Any", + "tags": [], + "label": "context", + "description": [], + "signature": [ + "any" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "unifiedFieldList", "id": "def-public.FieldVisualizeButton", @@ -2008,121 +2062,119 @@ }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps", + "id": "def-public.FieldStatsResponse", "type": "Interface", "tags": [], - "label": "FieldStatsProps", + "label": "FieldStatsResponse", "description": [], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "signature": [ + "FieldStatsResponse", + "" + ], + "path": "src/plugins/unified_field_list/common/types/stats.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.services", - "type": "Object", + "id": "def-public.FieldStatsResponse.totalDocuments", + "type": "number", "tags": [], - "label": "services", + "label": "totalDocuments", "description": [], "signature": [ - { - "pluginId": "unifiedFieldList", - "scope": "public", - "docId": "kibUnifiedFieldListPluginApi", - "section": "def-public.FieldStatsServices", - "text": "FieldStatsServices" - } + "number | undefined" ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "path": "src/plugins/unified_field_list/common/types/stats.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.query", - "type": "CompoundType", + "id": "def-public.FieldStatsResponse.sampledDocuments", + "type": "number", "tags": [], - "label": "query", + "label": "sampledDocuments", "description": [], "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - " | ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.AggregateQuery", - "text": "AggregateQuery" - } + "number | undefined" ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "path": "src/plugins/unified_field_list/common/types/stats.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.filters", - "type": "Array", + "id": "def-public.FieldStatsResponse.sampledValues", + "type": "number", "tags": [], - "label": "filters", + "label": "sampledValues", "description": [], "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]" + "number | undefined" ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "path": "src/plugins/unified_field_list/common/types/stats.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.fromDate", - "type": "string", + "id": "def-public.FieldStatsResponse.histogram", + "type": "Object", "tags": [], - "label": "fromDate", + "label": "histogram", "description": [], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "signature": [ + "BucketedAggregation", + " | undefined" + ], + "path": "src/plugins/unified_field_list/common/types/stats.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.toDate", - "type": "string", + "id": "def-public.FieldStatsResponse.topValues", + "type": "Object", "tags": [], - "label": "toDate", + "label": "topValues", "description": [], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "signature": [ + "BucketedAggregation", + " | undefined" + ], + "path": "src/plugins/unified_field_list/common/types/stats.ts", "deprecated": false, "trackAdoption": false - }, + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldStatsServices", + "type": "Interface", + "tags": [], + "label": "FieldStatsServices", + "description": [], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.dataViewOrDataViewId", - "type": "CompoundType", + "id": "def-public.FieldStatsServices.uiSettings", + "type": "Object", "tags": [], - "label": "dataViewOrDataViewId", + "label": "uiSettings", "description": [], "signature": [ - "string | ", { - "pluginId": "dataViews", + "pluginId": "@kbn/core-ui-settings-browser", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibKbnCoreUiSettingsBrowserPluginApi", + "section": "def-common.IUiSettingsClient", + "text": "IUiSettingsClient" } ], "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", @@ -2131,18 +2183,18 @@ }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.field", + "id": "def-public.FieldStatsServices.dataViews", "type": "Object", "tags": [], - "label": "field", + "label": "dataViews", "description": [], "signature": [ { "pluginId": "dataViews", - "scope": "common", + "scope": "public", "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewField", - "text": "DataViewField" + "section": "def-public.DataViewsServicePublic", + "text": "DataViewsServicePublic" } ], "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", @@ -2151,27 +2203,19 @@ }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.color", - "type": "string", - "tags": [], - "label": "color", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.datatestsubj", - "type": "string", + "id": "def-public.FieldStatsServices.data", + "type": "Object", "tags": [], - "label": "'data-test-subj'", + "label": "data", "description": [], "signature": [ - "string | undefined" + { + "pluginId": "data", + "scope": "public", + "docId": "kibDataPluginApi", + "section": "def-public.DataPublicPluginStart", + "text": "DataPublicPluginStart" + } ], "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", "deprecated": false, @@ -2179,150 +2223,49 @@ }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.overrideMissingContent", - "type": "Function", + "id": "def-public.FieldStatsServices.fieldFormats", + "type": "CompoundType", "tags": [], - "label": "overrideMissingContent", + "label": "fieldFormats", "description": [], "signature": [ - "((params: { element: JSX.Element; reason: \"no-data\" | \"unsupported\"; }) => JSX.Element | null) | undefined" - ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ + "Omit<", { - "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.overrideMissingContent.$1", - "type": "Object", - "tags": [], - "label": "params", - "description": [], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.overrideMissingContent.$1.element", - "type": "Object", - "tags": [], - "label": "element", - "description": [], - "signature": [ - "JSX.Element" - ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.overrideMissingContent.$1.reason", - "type": "CompoundType", - "tags": [], - "label": "reason", - "description": [], - "signature": [ - "\"no-data\" | \"unsupported\"" - ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [] - }, - { - "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.overrideFooter", - "type": "Function", - "tags": [], - "label": "overrideFooter", - "description": [], - "signature": [ - "((params: { element: JSX.Element; totalDocuments?: number | undefined; sampledDocuments?: number | undefined; }) => JSX.Element) | undefined" + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FieldFormatsRegistry", + "text": "FieldFormatsRegistry" + }, + ", \"init\" | \"register\"> & { deserialize: ", + { + "pluginId": "fieldFormats", + "scope": "common", + "docId": "kibFieldFormatsPluginApi", + "section": "def-common.FormatFactory", + "text": "FormatFactory" + }, + "; }" ], "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.overrideFooter.$1", - "type": "Object", - "tags": [], - "label": "params", - "description": [], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.overrideFooter.$1.element", - "type": "Object", - "tags": [], - "label": "element", - "description": [], - "signature": [ - "JSX.Element" - ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.overrideFooter.$1.totalDocuments", - "type": "number", - "tags": [], - "label": "totalDocuments", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.overrideFooter.$1.sampledDocuments", - "type": "number", - "tags": [], - "label": "sampledDocuments", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsProps.onAddFilter", - "type": "Function", + "id": "def-public.FieldStatsServices.charts", + "type": "Object", "tags": [], - "label": "onAddFilter", + "label": "charts", "description": [], "signature": [ { - "pluginId": "unifiedFieldList", + "pluginId": "charts", "scope": "public", - "docId": "kibUnifiedFieldListPluginApi", - "section": "def-public.AddFieldFilterHandler", - "text": "AddFieldFilterHandler" - }, - " | undefined" + "docId": "kibChartsPluginApi", + "section": "def-public.ChartsPluginSetup", + "text": "ChartsPluginSetup" + } ], "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", "deprecated": false, @@ -2333,22 +2276,29 @@ }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsResponse", + "id": "def-public.FieldStatsState", "type": "Interface", "tags": [], - "label": "FieldStatsResponse", + "label": "FieldStatsState", "description": [], - "signature": [ - "FieldStatsResponse", - "" - ], - "path": "src/plugins/unified_field_list/common/types/stats.ts", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsResponse.totalDocuments", + "id": "def-public.FieldStatsState.isLoading", + "type": "boolean", + "tags": [], + "label": "isLoading", + "description": [], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldStatsState.totalDocuments", "type": "number", "tags": [], "label": "totalDocuments", @@ -2356,13 +2306,13 @@ "signature": [ "number | undefined" ], - "path": "src/plugins/unified_field_list/common/types/stats.ts", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsResponse.sampledDocuments", + "id": "def-public.FieldStatsState.sampledDocuments", "type": "number", "tags": [], "label": "sampledDocuments", @@ -2370,13 +2320,13 @@ "signature": [ "number | undefined" ], - "path": "src/plugins/unified_field_list/common/types/stats.ts", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsResponse.sampledValues", + "id": "def-public.FieldStatsState.sampledValues", "type": "number", "tags": [], "label": "sampledValues", @@ -2384,37 +2334,37 @@ "signature": [ "number | undefined" ], - "path": "src/plugins/unified_field_list/common/types/stats.ts", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsResponse.histogram", + "id": "def-public.FieldStatsState.histogram", "type": "Object", "tags": [], "label": "histogram", "description": [], "signature": [ "BucketedAggregation", - " | undefined" + " | undefined" ], - "path": "src/plugins/unified_field_list/common/types/stats.ts", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsResponse.topValues", + "id": "def-public.FieldStatsState.topValues", "type": "Object", "tags": [], "label": "topValues", "description": [], "signature": [ "BucketedAggregation", - " | undefined" + " | undefined" ], - "path": "src/plugins/unified_field_list/common/types/stats.ts", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", "deprecated": false, "trackAdoption": false } @@ -2423,122 +2373,198 @@ }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsServices", + "id": "def-public.FieldTopValuesBucketParams", "type": "Interface", "tags": [], - "label": "FieldStatsServices", + "label": "FieldTopValuesBucketParams", "description": [], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsServices.uiSettings", + "id": "def-public.FieldTopValuesBucketParams.field", "type": "Object", "tags": [], - "label": "uiSettings", + "label": "field", "description": [], "signature": [ { - "pluginId": "@kbn/core-ui-settings-browser", + "pluginId": "dataViews", "scope": "common", - "docId": "kibKbnCoreUiSettingsBrowserPluginApi", - "section": "def-common.IUiSettingsClient", - "text": "IUiSettingsClient" + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" } ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsServices.dataViews", - "type": "Object", + "id": "def-public.FieldTopValuesBucketParams.fieldValue", + "type": "Unknown", "tags": [], - "label": "dataViews", + "label": "fieldValue", "description": [], "signature": [ - { - "pluginId": "dataViews", - "scope": "public", - "docId": "kibDataViewsPluginApi", - "section": "def-public.DataViewsServicePublic", - "text": "DataViewsServicePublic" - } + "unknown" ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsServices.data", - "type": "Object", + "id": "def-public.FieldTopValuesBucketParams.formattedFieldValue", + "type": "string", "tags": [], - "label": "data", + "label": "formattedFieldValue", "description": [], "signature": [ - { - "pluginId": "data", - "scope": "public", - "docId": "kibDataPluginApi", - "section": "def-public.DataPublicPluginStart", - "text": "DataPublicPluginStart" - } + "string | undefined" ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsServices.fieldFormats", + "id": "def-public.FieldTopValuesBucketParams.formattedPercentage", + "type": "string", + "tags": [], + "label": "formattedPercentage", + "description": [], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldTopValuesBucketParams.progressValue", + "type": "number", + "tags": [], + "label": "progressValue", + "description": [], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldTopValuesBucketParams.count", + "type": "number", + "tags": [], + "label": "count", + "description": [], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldTopValuesBucketParams.color", + "type": "string", + "tags": [], + "label": "color", + "description": [], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldTopValuesBucketParams.type", "type": "CompoundType", "tags": [], - "label": "fieldFormats", + "label": "type", "description": [], "signature": [ - "Omit<", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FieldFormatsRegistry", - "text": "FieldFormatsRegistry" - }, - ", \"init\" | \"register\"> & { deserialize: ", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.FormatFactory", - "text": "FormatFactory" - }, - "; }" + "\"normal\" | \"other\" | undefined" ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", "deprecated": false, "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldTopValuesBucketProps", + "type": "Interface", + "tags": [], + "label": "FieldTopValuesBucketProps", + "description": [], + "signature": [ + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.FieldTopValuesBucketProps", + "text": "FieldTopValuesBucketProps" }, + " extends ", + { + "pluginId": "unifiedFieldList", + "scope": "public", + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.FieldTopValuesBucketParams", + "text": "FieldTopValuesBucketParams" + } + ], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "unifiedFieldList", - "id": "def-public.FieldStatsServices.charts", - "type": "Object", + "id": "def-public.FieldTopValuesBucketProps.datatestsubj", + "type": "string", "tags": [], - "label": "charts", + "label": "'data-test-subj'", + "description": [], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldTopValuesBucketProps.onAddFilter", + "type": "Function", + "tags": [], + "label": "onAddFilter", "description": [], "signature": [ { - "pluginId": "charts", + "pluginId": "unifiedFieldList", "scope": "public", - "docId": "kibChartsPluginApi", - "section": "def-public.ChartsPluginSetup", - "text": "ChartsPluginSetup" - } + "docId": "kibUnifiedFieldListPluginApi", + "section": "def-public.AddFieldFilterHandler", + "text": "AddFieldFilterHandler" + }, + " | undefined" ], - "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldTopValuesBucketProps.overrideFieldTopValueBar", + "type": "Function", + "tags": [], + "label": "overrideFieldTopValueBar", + "description": [ + "\nOptional callback to allow overriding props on bucket level" + ], + "signature": [ + "OverrideFieldTopValueBarCallback", + " | undefined" + ], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx", "deprecated": false, "trackAdoption": false } @@ -3444,6 +3470,23 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "unifiedFieldList", + "id": "def-public.FieldStatsProps", + "type": "Type", + "tags": [], + "label": "FieldStatsProps", + "description": [], + "signature": [ + "FieldStatsWithKbnQuery", + " | ", + "FieldStatsWithDslQuery" + ], + "path": "src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [], diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index e8b3b304ae5952..35bbdde254eeb3 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-disco | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 192 | 0 | 187 | 4 | +| 196 | 0 | 188 | 7 | ## Client diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 2c0e4c76e2237b..f2f1cda8132a5b 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 52869c4dababd6..ae5d7e342b4490 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-11-15 +date: 2022-11-17 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 bea702c5de6bd9..f1e69211deb5e2 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-11-15 +date: 2022-11-17 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 4a391e98fe3f35..5456b143ed9b99 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-11-15 +date: 2022-11-17 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 8bfe2646575604..27748e7d6a5bf6 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-11-15 +date: 2022-11-17 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 0c474cc7d488b8..ebc6f3501c17df 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-11-15 +date: 2022-11-17 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 d8894f1e8f9334..04ea33fbf56c0d 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-11-15 +date: 2022-11-17 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 ab5ec8b48b1466..fc78738d713704 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-11-15 +date: 2022-11-17 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 86bcf1e39059f5..ce3ebe28bb6f46 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-11-15 +date: 2022-11-17 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 36106b22d469f4..e02fe4c1922b7b 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-11-15 +date: 2022-11-17 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 e4351ff278d336..dbf16361384934 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-11-15 +date: 2022-11-17 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 5a315366f2127e..dfdab920c276a9 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-11-15 +date: 2022-11-17 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 253f544616f1ba..85e8dd1bb3f253 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-11-15 +date: 2022-11-17 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 d77468762eca47..8605cf20dbc9a5 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-11-15 +date: 2022-11-17 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 92e0c892b1d9fa..db2b89eba9c691 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-11-15 +date: 2022-11-17 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 f19ead519a07a0..b5ce4115e0bd84 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index d31bb4f8807e85..e498e7ab1be33c 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-11-15 +date: 2022-11-17 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/docs/api/actions-and-connectors/create.asciidoc b/docs/api/actions-and-connectors/create.asciidoc index c33cbdd77232c3..b277a49d43723c 100644 --- a/docs/api/actions-and-connectors/create.asciidoc +++ b/docs/api/actions-and-connectors/create.asciidoc @@ -83,6 +83,18 @@ For more information, refer to <>. For more information, refer to <>. ===== +.Opsgenie connectors +[%collapsible%open] +===== + +`apiUrl`:: +(Required, string) The Opsgenie URL. For example, `https://api.opsgenie.com` or +`https://api.eu.opsgenie.com`. If you are using the `xpack.actions.allowedHosts` +setting, make sure the hostname is added to the allowed hosts. + +For more information, refer to <>. +===== + .{sn-itom}, {sn-itsm}, and {sn-sir} connectors [%collapsible%open] ===== @@ -408,7 +420,7 @@ For more configuration properties, refer to <>. `connector_type_id`:: (Required, string) The connector type ID for the connector. For example, -`.cases-webhook`, `.index`, `.jira`, `.server-log`, or `.servicenow-itom`. +`.cases-webhook`, `.index`, `.jira`, `.opsgenie`, `.server-log`, or `.servicenow-itom`. `name`:: (Required, string) The display name for the connector. @@ -447,6 +459,14 @@ authentication. (Required, string) The account email for HTTP Basic authentication. ===== +.Opsgenie connectors +[%collapsible%open] +===== +`apiKey`:: +(Required, string) The Opsgenie API authentication key for HTTP Basic +authentication. +===== + .{sn-itom}, {sn-itsm}, and {sn-sir} connectors [%collapsible%open] ===== diff --git a/docs/api/spaces-management.asciidoc b/docs/api/spaces-management.asciidoc index 333a06cf3754e5..91b7ae349b9a80 100644 --- a/docs/api/spaces-management.asciidoc +++ b/docs/api/spaces-management.asciidoc @@ -22,6 +22,10 @@ The following {kib} spaces APIs are available: * <> to disable legacy URL aliases if an error is encountered +* <> to update one or more saved objects to add and/or remove them from specified spaces + +* <> to collect references and spaces context for saved objects + include::spaces-management/post.asciidoc[] include::spaces-management/put.asciidoc[] include::spaces-management/get.asciidoc[] @@ -30,3 +34,5 @@ include::spaces-management/delete.asciidoc[] include::spaces-management/copy_saved_objects.asciidoc[] include::spaces-management/resolve_copy_saved_objects_conflicts.asciidoc[] include::spaces-management/disable_legacy_url_aliases.asciidoc[] +include::spaces-management/update_objects_spaces.asciidoc[] +include::spaces-management/get_shareable_references.asciidoc[] diff --git a/docs/api/spaces-management/get_shareable_references.asciidoc b/docs/api/spaces-management/get_shareable_references.asciidoc new file mode 100644 index 00000000000000..8066736c0c15d6 --- /dev/null +++ b/docs/api/spaces-management/get_shareable_references.asciidoc @@ -0,0 +1,81 @@ +[role="xpack"] +[[spaces-api-get-shareable-references]] +=== Get shareable references API +++++ +Get shareable references +++++ + +experimental[] Get shareable references. + +Collects references and spaces context for saved objects. + +[[spaces-api-get-shareable-references-request]] +==== {api-request-title} + +`POST :/api/spaces/_get_shareable_references` + +[[spaces-api-get-shareable-references-request-body]] +==== {api-request-body-title} + +`objects`:: + (Required, object array) The saved objects to collect outbound references for. ++ +.Properties of `objects` +[%collapsible%open] +===== + `type`::: + (Required, string) The saved object type. + + `id`::: + (Required, string) The saved object ID. +===== + +[role="child_attributes"] +[[spaces-api-get-shareable-references-response-body]] +==== {api-response-body-title} + +`objects`:: + (object array) The returned input object or one of its references, with additional context. ++ +.Properties of `objects` +[%collapsible%open] +===== + `type`::: + (string) The saved object type. + + `id`::: + (string) The saved object ID. + + `originId`::: + (string) The origin ID of the referenced object (if it has one). + + `inboundReferences`::: + (object array) References to this object. ++ +NOTE: This does not contain _all inbound references everywhere_, it only contains inbound references to this object within the scope of this operation. ++ +.Properties of `inboundReferences` +[%collapsible%open] +====== + `type`:::: + (string) The type of the object that has the inbound reference. + + `id`:::: + (string) The ID of the object that has the inbound reference. + + `name`:::: + (string) The name of the inbound reference. +====== + + `spaces`::: + (string array) The space(s) that the referenced saved object exists in. + + `spacesWithMatchingAliases`::: + (string array) The space(s) that legacy URL aliases matching this type/id exist in. (if there are any) + + `spacesWithMatchingOrigins`::: + (string array) The space(s) that objects matching this origin exist in (including this one). (if there are any) + + `isMissing`::: + (boolean) Whether or not this object or reference is missing. +===== diff --git a/docs/api/spaces-management/update_objects_spaces.asciidoc b/docs/api/spaces-management/update_objects_spaces.asciidoc new file mode 100644 index 00000000000000..dec846fd6fee0d --- /dev/null +++ b/docs/api/spaces-management/update_objects_spaces.asciidoc @@ -0,0 +1,142 @@ +[role="xpack"] +[[spaces-api-update-objects-spaces]] +=== Update saved objects spaces API +++++ +Update saved objects spaces +++++ + +experimental[] Update saved objects spaces. + +Updates one or more saved objects to add and/or remove them from specified spaces. + +[[spaces-api-update-objects-spaces-request]] +==== {api-request-title} + +`POST :/api/spaces/_update_objects_spaces` + +[[spaces-api-update-objects-spaces-request-body]] +==== {api-request-body-title} + +`objects`:: + (Required, object array) The saved objects to update. ++ +.Properties of `objects` +[%collapsible%open] +===== + `type`::: + (Required, string) The saved object type. + + `id`::: + (Required, string) The saved object ID. +===== + +`spacesToAdd`:: + (Required, string array) The IDs of the spaces the specified objects should be added to. + +`spacesToRemove`:: + (Required, string array) The IDs of the spaces the specified objects should be removed from. + +[role="child_attributes"] +[[spaces-api-update-objects-spaces-response-body]] +==== {api-response-body-title} + +`objects`:: + (object array) The saved objects that have been updated. ++ +.Properties of `objects` +[%collapsible%open] +===== + `type`::: + (string) The saved object type. + + `id`::: + (string) The saved object ID. + + `spaces`::: + (string array) The space(s) that the referenced saved object exists in. + + `errors`::: + (string) Included if there was an error updating this object's spaces. +===== + +[[spaces-api-update-objects-spaces-example]] +==== {api-examples-title} + +[[spaces-api-update-objects-spaces-example-1]] +===== Sharing saved objects + +To share a saved object to a space programmatically follow these steps: + +1. Collect reference graph and spaces context for each saved object that you want to share using <>: ++ +[source,sh] +---- +$ curl -X POST /api/spaces/_get_shareable_references +{ + "objects": [ + { + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + } + ] +} +---- ++ +The API returns the following: ++ +[source,json] +---- +{ + "objects": [ + { + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "spaces": ["default"], + "inboundReferences": [], + "spacesWithMatchingOrigins": ["default"] + } + ] +} +---- + +2. Check each saved object for `spacesWithMatchingOrigins` conflicts. ++ +Objects should not be shared to spaces with matching origins or you will create URL conflicts (causing the same URL to point to different saved objects). + +3. Check each saved object for `spacesWithMatchingAliases` conflicts. ++ +If these match the space(s) that these saved objects will be shared to you should disable legacy URL aliases for them using <>. ++ +When sharing to all spaces (`*`) all entries in `spacesWithMatchingAliases` should be checked. + +4. Update spaces of each saved object and all its references: ++ +[source,sh] +---- +$ curl -X POST /api/spaces/_update_objects_spaces +{ + "objects": [ + { + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247" + } + ], + "spacesToAdd": ["test"], + "spacesToRemove": [] +} +---- ++ +The API returns the following: ++ +[source,json] +---- +{ + "objects": [ + { + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "spaces": ["default", "test"] + } + ] +} +---- diff --git a/docs/developer/best-practices/index.asciidoc b/docs/developer/best-practices/index.asciidoc index c3f8239e9af918..c610c787d2b8d1 100644 --- a/docs/developer/best-practices/index.asciidoc +++ b/docs/developer/best-practices/index.asciidoc @@ -22,7 +22,7 @@ Are you planning with scalability in mind? Did you know {kib} makes a public statement about our commitment to creating an accessible product for people with disabilities? -https://www.elastic.co/guide/en/kibana/master/accessibility.html[We do]! +<>! It’s very important all of our apps are accessible. * Learn how https://elastic.github.io/eui/#/guidelines/accessibility[EUI diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index b8d50719a66481..9aa871ebf334b1 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -177,7 +177,7 @@ for use in their own application. |{kib-repo}blob/{branch}/src/plugins/files/README.md[files] -|File upload, download, sharing, and serving over HTTP implementation in Kibana. +|The files service provides functionality to manage, retrieve, share files in Kibana. |{kib-repo}blob/{branch}/src/plugins/guided_onboarding/README.md[guidedOnboarding] diff --git a/docs/maps/asset-tracking-tutorial.asciidoc b/docs/maps/asset-tracking-tutorial.asciidoc index 4e6efff35b3a35..f33ce2ef7547ea 100644 --- a/docs/maps/asset-tracking-tutorial.asciidoc +++ b/docs/maps/asset-tracking-tutorial.asciidoc @@ -8,7 +8,7 @@ In this tutorial, you’ll look at live urban transit data from the city of Port You’ll learn to: -- Use {filebeat} to ingest the TriMet REST API into Elasticsearch. +- Use {agent} to ingest the TriMet REST API into {es}. - Create a map with layers that visualize asset tracks and last-known locations. - Use symbols and colors to style data values and show which direction an asset is heading. - Set up tracking containment alerts to monitor moving vehicles. @@ -23,11 +23,11 @@ image::maps/images/asset-tracking-tutorial/construction_zones.png[] - If you don’t already have {kib}, set it up with https://www.elastic.co/cloud/elasticsearch-service/signup?baymax=docs-body&elektra=docs[our free trial]. Download the deployment credentials. - Obtain an API key for https://developer.trimet.org/[TriMet web services] at https://developer.trimet.org/appid/registration/. -- https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-installation-configuration.html[Install Filebeat]. +- {fleet-guide}/fleet-overview.html[Fleet] is enabled on your cluster, and one or more {fleet-guide}/elastic-agent-installation.html[{agent}s] is enrolled. [float] === Part 1: Ingest the Portland bus data -To get to the fun of visualizing and alerting on Portland buses, you must first create a {filebeat} input to ingest the TriMet Portland bus data into {es}. +To get to the fun of visualizing and alerting on Portland buses, you must first add the *Custom API* integration to an Elastic Agent policy to get the TriMet Portland bus data into {es}. [float] ==== Step 1: Set up an Elasticsearch index @@ -270,47 +270,39 @@ PUT _ingest/pipeline/tri_met_tracks ---------------------------------- [float] -==== Step 2: Start {filebeat} +==== Step 2: Configure {agent} -. Replace the contents in your `filebeat.yml` file with the following: -+ -[source,yaml] ----------------------------------- -filebeat.inputs: -# Fetch trimet bus data every minute. -- type: httpjson - interval: 1m - request.url: "https://developer.trimet.org/ws/v2/vehicles?appID=" - response.split: - target: body.resultSet.vehicle - processors: - - decode_json_fields: - fields: ["message"] - target: "trimet" +. From the {kib} main menu, click *Fleet*, then the *Agent policies* tab. - pipeline: "tri_met_tracks" +. Click the name of the agent policy where you want to add the *Custom API* integration. The configuration changes you make only apply to the policy you select. +. Click the name of the *Custom API* integration, or add the integration if the agent policy does not yet have it. -# ---------------------------- Elastic Cloud Output ---------------------------- -cloud.id: -cloud.auth: +. From the *Edit Custom API integration* page, expand the *Change defaults* section. ----------------------------------- +. Set the *Dataset name* to *httpjson.trimet*. + +. Set the *Ingest Pipeline* to *tri_met_pipeline*. -. Replace `` with your TriMet application id. -. Replace `` with your Elastic Cloud deployment credentials. -. Replace `` with your {ece}/ece-cloud-id.html[elastic cloud id]. -. Open a terminal window, and then navigate to the {filebeat} folder. -. In your `filebeat` folder, run {filebeat} with the edited config: +. Set the *Request URL* to *https://developer.trimet.org/ws/v2/vehicles?appID=*. + +. Set *Response Split* to *target: body.resultSet.vehicle*. + +. At the bottom of the configuration, expand *Advanced options*. + +. Set *Processors* to: + -[source,bash] +[source,yaml] ---------------------------------- -/bin/filebeat -c filebeat.yml +- decode_json_fields: + fields: ["message"] + target: "trimet" ---------------------------------- -. Wait for {filebeat} to start shipping data to Elastic Cloud. {filebeat} should not produce any output to stdout. +. Leave everything else as defaults. + +. Click *Save integration* to deploy the configuration to any {agent} with the policy assigned. -. Leave the terminal window open and {filebeat} running throughout this tutorial. [float] ==== Step 3: Create a data view for the tri_met_tracks {es} index diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index 9e1ee62f093fe3..f459150b0ee8d9 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -13,7 +13,7 @@ All integrations are available in a single view, and image::images/add-integration.png[Integrations page from which you can choose integrations to start collecting and analyzing data] NOTE: When an integration is available for both -https://www.elastic.co/guide/en/fleet/master/beats-agent-comparison.html[Elastic Agent and Beats], +{fleet-guide}/beats-agent-comparison.html[Elastic Agent and Beats], the *Integrations* view defaults to the Elastic Agent integration, if it is generally available (GA). To show a diff --git a/docs/user/alerting/alerting-troubleshooting.asciidoc b/docs/user/alerting/alerting-troubleshooting.asciidoc index 9d254ce5fc834a..62604c465dafc2 100644 --- a/docs/user/alerting/alerting-troubleshooting.asciidoc +++ b/docs/user/alerting/alerting-troubleshooting.asciidoc @@ -83,12 +83,11 @@ The result of this HTTP request (and printed to stdout by https://github.com/pmu [[alerting-error-banners]] === Look for error banners -The *Rule Management* and *Rule Details* pages contain an error banner, which helps to identify the errors for the rules: -[role="screenshot"] -image::images/rules-management-health.png[Rule management page with the errors banner] +The **{stack-manage-app}** > *{rules-ui}* page contains an error banner that +helps to identify the errors for the rules: [role="screenshot"] -image::images/rules-details-health.png[Rule details page with the errors banner] +image::images/rules-management-health.png[Rule management page with the errors banner] [float] [[task-manager-diagnostics]] diff --git a/docs/user/alerting/images/rules-details-health.png b/docs/user/alerting/images/rules-details-health.png deleted file mode 100644 index ffdac4fcd19839..00000000000000 Binary files a/docs/user/alerting/images/rules-details-health.png and /dev/null differ diff --git a/docs/user/alerting/images/rules-management-health.png b/docs/user/alerting/images/rules-management-health.png index e81c4e07dd7b2e..edd16b245ec654 100644 Binary files a/docs/user/alerting/images/rules-management-health.png and b/docs/user/alerting/images/rules-management-health.png differ diff --git a/examples/field_formats_example/tsconfig.json b/examples/field_formats_example/tsconfig.json index 66e9d7db028c75..a7651b649e5b3d 100644 --- a/examples/field_formats_example/tsconfig.json +++ b/examples/field_formats_example/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/examples/files_example/tsconfig.json b/examples/files_example/tsconfig.json index 2ce0ddb8f7d66e..9329f941c10065 100644 --- a/examples/files_example/tsconfig.json +++ b/examples/files_example/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target/types" }, diff --git a/examples/hello_world/tsconfig.json b/examples/hello_world/tsconfig.json index f0741719540484..6cfb28f7b33171 100644 --- a/examples/hello_world/tsconfig.json +++ b/examples/hello_world/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target/types" }, diff --git a/examples/partial_results_example/tsconfig.json b/examples/partial_results_example/tsconfig.json index ba03cbc8361896..97d4c752cc3b56 100644 --- a/examples/partial_results_example/tsconfig.json +++ b/examples/partial_results_example/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.json", + "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./target", "skipLibCheck": true diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index 17222ef4a5266e..4c8bcb3cd31920 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -327,6 +327,9 @@ { "id": "kibFileUploadPluginApi" }, + { + "id": "kibFilesPluginApi" + }, { "id": "kibFleetPluginApi" }, diff --git a/package.json b/package.json index 7ad2173ff9f153..236ac8087855b8 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dashboarding" ], "private": true, - "version": "8.6.0", + "version": "8.7.0", "branch": "main", "types": "./kibana.d.ts", "tsdocMetadata": "./build/tsdoc-metadata.json", diff --git a/packages/analytics/client/README.md b/packages/analytics/client/README.md index f5cc0d5efa889c..e51795faa6a03a 100644 --- a/packages/analytics/client/README.md +++ b/packages/analytics/client/README.md @@ -133,6 +133,15 @@ analytics.optIn({ }) ``` +### Explicit flush of the events + +If, at any given point (usually testing or during shutdowns) we need to make sure that all the pending events +in the queue are sent. The `flush` API returns a promise that will resolve as soon as all events in the queue are sent. + +```typescript +await analytics.flush() +``` + ### Shipping events In order to report the event to an analytics tool, we need to register the shippers our application wants to use. To register a shipper use the API `registerShipper`: 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 601f94aa1e243e..efe32683ee468d 100644 --- a/packages/analytics/client/src/analytics_client/analytics_client.test.ts +++ b/packages/analytics/client/src/analytics_client/analytics_client.test.ts @@ -30,8 +30,8 @@ describe('AnalyticsClient', () => { }); }); - afterEach(() => { - analyticsClient.shutdown(); + afterEach(async () => { + await analyticsClient.shutdown(); jest.useRealTimers(); }); @@ -381,7 +381,7 @@ describe('AnalyticsClient', () => { test( 'Handles errors in the shipper', - fakeSchedulers((advance) => { + fakeSchedulers(async (advance) => { const optInMock = jest.fn().mockImplementation(() => { throw new Error('Something went terribly wrong'); }); @@ -404,7 +404,7 @@ describe('AnalyticsClient', () => { `Shipper "${MockedShipper.shipperName}" failed to extend the context`, expect.any(Error) ); - expect(() => analyticsClient.shutdown()).not.toThrow(); + await expect(analyticsClient.shutdown()).resolves.toBeUndefined(); expect(shutdownMock).toHaveBeenCalled(); }) ); diff --git a/packages/analytics/client/src/analytics_client/analytics_client.ts b/packages/analytics/client/src/analytics_client/analytics_client.ts index 57741f098c6acc..9e0c559cbdc556 100644 --- a/packages/analytics/client/src/analytics_client/analytics_client.ts +++ b/packages/analytics/client/src/analytics_client/analytics_client.ts @@ -238,7 +238,20 @@ export class AnalyticsClient implements IAnalyticsClient { this.shipperRegistered$.next(); }; - public shutdown = () => { + public flush = async () => { + await Promise.all( + [...this.shippersRegistry.allShippers.entries()].map(async ([shipperName, shipper]) => { + try { + await shipper.flush(); + } catch (err) { + this.initContext.logger.warn(`Failed to flush shipper "${shipperName}"`, err); + } + }) + ); + }; + + public shutdown = async () => { + await this.flush(); this.shippersRegistry.allShippers.forEach((shipper, shipperName) => { try { shipper.shutdown(); diff --git a/packages/analytics/client/src/analytics_client/mocks.ts b/packages/analytics/client/src/analytics_client/mocks.ts index 221ca0ff3872fe..d09bfa67dee82f 100644 --- a/packages/analytics/client/src/analytics_client/mocks.ts +++ b/packages/analytics/client/src/analytics_client/mocks.ts @@ -18,6 +18,7 @@ function createMockedAnalyticsClient(): jest.Mocked { removeContextProvider: jest.fn(), registerShipper: jest.fn(), telemetryCounter$: new Subject(), + flush: jest.fn(), shutdown: jest.fn(), }; } diff --git a/packages/analytics/client/src/analytics_client/types.ts b/packages/analytics/client/src/analytics_client/types.ts index 9a25f821b70a3f..5726bf00466879 100644 --- a/packages/analytics/client/src/analytics_client/types.ts +++ b/packages/analytics/client/src/analytics_client/types.ts @@ -216,7 +216,11 @@ export interface IAnalyticsClient { */ readonly telemetryCounter$: Observable; /** - * Stops the client. + * Forces all shippers to send all their enqueued events and fulfills the returned promise. */ - shutdown: () => void; + flush: () => Promise; + /** + * Stops the client. Flushing any pending events in the process. + */ + shutdown: () => Promise; } diff --git a/packages/analytics/client/src/shippers/mocks.ts b/packages/analytics/client/src/shippers/mocks.ts index 4660ae9d27e725..fccdd4788f7d97 100644 --- a/packages/analytics/client/src/shippers/mocks.ts +++ b/packages/analytics/client/src/shippers/mocks.ts @@ -23,6 +23,7 @@ class MockedShipper implements IShipper { public reportEvents = jest.fn(); public extendContext = jest.fn(); public telemetryCounter$ = new Subject(); + public flush = jest.fn(); public shutdown = jest.fn(); } diff --git a/packages/analytics/client/src/shippers/types.ts b/packages/analytics/client/src/shippers/types.ts index 67fe2c54bd77ed..c1e2ab8a811531 100644 --- a/packages/analytics/client/src/shippers/types.ts +++ b/packages/analytics/client/src/shippers/types.ts @@ -32,6 +32,10 @@ export interface IShipper { * Observable to emit the stats of the processed events. */ telemetryCounter$?: Observable; + /** + * Sends all the enqueued events and fulfills the returned promise. + */ + flush: () => Promise; /** * Shutdown the shipper. */ 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 47728a99a511ae..e82ff3c45b1fac 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 @@ -161,6 +161,58 @@ describe('ElasticV3BrowserShipper', () => { }) ); + test( + 'calls to flush forces the client to send all the pending events', + fakeSchedulers(async (advance) => { + shipper.optIn(true); + shipper.reportEvents(events); + const counter = firstValueFrom(shipper.telemetryCounter$); + const promise = shipper.flush(); + advance(0); // bufferWhen requires some sort of fake scheduling to advance (but we are not advancing 1s) + await promise; + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + keepalive: true, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` + Object { + "code": "200", + "count": 1, + "event_type": "test-event-type", + "source": "elastic_v3_browser", + "type": "succeeded", + } + `); + }) + ); + + test('calls to flush resolve immediately if there is nothing to send', async () => { + shipper.optIn(true); + await shipper.flush(); + expect(fetchMock).toHaveBeenCalledTimes(0); + }); + + test('calling flush multiple times does not keep hanging', async () => { + await expect(shipper.flush()).resolves.toBe(undefined); + await expect(shipper.flush()).resolves.toBe(undefined); + await Promise.all([shipper.flush(), shipper.flush()]); + }); + + test('calling flush after shutdown does not keep hanging', async () => { + shipper.shutdown(); + await expect(shipper.flush()).resolves.toBe(undefined); + }); + test('calls to reportEvents call `fetch` when shutting down if optIn value is set to true', async () => { shipper.reportEvents(events); shipper.optIn(true); diff --git a/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.ts b/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.ts index 53f19910eab752..185ce37072be05 100644 --- a/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.ts +++ b/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.ts @@ -6,7 +6,17 @@ * Side Public License, v 1. */ -import { BehaviorSubject, interval, Subject, bufferWhen, concatMap, filter, skipWhile } from 'rxjs'; +import { + BehaviorSubject, + interval, + Subject, + bufferWhen, + concatMap, + skipWhile, + firstValueFrom, + map, + merge, +} from 'rxjs'; import type { AnalyticsClientInitContext, Event, @@ -39,6 +49,8 @@ export class ElasticV3BrowserShipper implements IShipper { private readonly url: string; private readonly internalQueue$ = new Subject(); + private readonly flush$ = new Subject(); + private readonly queueFlushed$ = new Subject(); private readonly isOptedIn$ = new BehaviorSubject(undefined); private clusterUuid: string = 'UNKNOWN'; @@ -92,25 +104,48 @@ export class ElasticV3BrowserShipper implements IShipper { }); } + /** + * Triggers a flush of the internal queue to attempt to send any events held in the queue + * and resolves the returned promise once the queue is emptied. + */ + public async flush() { + if (this.flush$.isStopped) { + // If called after shutdown, return straight away + return; + } + + const promise = firstValueFrom(this.queueFlushed$); + this.flush$.next(); + await promise; + } + /** * Shuts down the shipper. * Triggers a flush of the internal queue to attempt to send any events held in the queue. */ public shutdown() { this.internalQueue$.complete(); // NOTE: When completing the observable, the buffer logic does not wait and releases any buffered events. + this.flush$.complete(); } private setUpInternalQueueSubscriber() { this.internalQueue$ .pipe( // Buffer events for 1 second or until we have an optIn value - bufferWhen(() => interval(1000).pipe(skipWhile(() => this.isOptedIn$.value === undefined))), - // Discard any events if we are not opted in - skipWhile(() => this.isOptedIn$.value === false), - // Skip empty buffers - filter((events) => events.length > 0), - // Send events - concatMap(async (events) => this.sendEvents(events)) + bufferWhen(() => + merge( + this.flush$, + interval(1000).pipe(skipWhile(() => this.isOptedIn$.value === undefined)) + ) + ), + // Send events (one batch at a time) + concatMap(async (events) => { + // Only send if opted-in and there's anything to send + if (this.isOptedIn$.value === true && events.length > 0) { + await this.sendEvents(events); + } + }), + map(() => this.queueFlushed$.next()) ) .subscribe(); } 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 091be6cb96e835..b9002892ec53e9 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 @@ -580,4 +580,45 @@ describe('ElasticV3ServerShipper', () => { ); }); }); + + describe('flush method', () => { + test('resolves straight away if it should not send anything', async () => { + await expect(shipper.flush()).resolves.toBe(undefined); + }); + + test('resolves when all the ongoing requests are complete', async () => { + shipper.optIn(true); + shipper.reportEvents(events); + expect(fetchMock).toHaveBeenCalledTimes(0); + fetchMock.mockImplementation(async () => { + // eslint-disable-next-line dot-notation + expect(shipper['inFlightRequests$'].value).toBe(1); + }); + await expect(shipper.flush()).resolves.toBe(undefined); + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + method: 'POST', + query: { debug: true }, + } + ); + }); + + test('calling flush multiple times does not keep hanging', async () => { + await expect(shipper.flush()).resolves.toBe(undefined); + await expect(shipper.flush()).resolves.toBe(undefined); + await Promise.all([shipper.flush(), shipper.flush()]); + }); + + test('calling flush after shutdown does not keep hanging', async () => { + shipper.shutdown(); + await expect(shipper.flush()).resolves.toBe(undefined); + }); + }); }); diff --git a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.ts b/packages/analytics/shippers/elastic_v3/server/src/server_shipper.ts index 34ebe134adcf72..cb6e689dd893ff 100644 --- a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.ts +++ b/packages/analytics/shippers/elastic_v3/server/src/server_shipper.ts @@ -22,6 +22,8 @@ import { BehaviorSubject, exhaustMap, mergeMap, + skip, + firstValueFrom, } from 'rxjs'; import type { AnalyticsClientInitContext, @@ -63,6 +65,8 @@ export class ElasticV3ServerShipper implements IShipper { private readonly internalQueue: Event[] = []; private readonly shutdown$ = new ReplaySubject(1); + private readonly flush$ = new Subject(); + private readonly inFlightRequests$ = new BehaviorSubject(0); private readonly isOptedIn$ = new BehaviorSubject(undefined); private readonly url: string; @@ -152,12 +156,33 @@ export class ElasticV3ServerShipper implements IShipper { this.internalQueue.push(...events); } + /** + * Triggers a flush of the internal queue to attempt to send any events held in the queue + * and resolves the returned promise once the queue is emptied. + */ + public async flush() { + if (this.flush$.isStopped) { + // If called after shutdown, return straight away + return; + } + + const promise = firstValueFrom( + this.inFlightRequests$.pipe( + skip(1), // Skipping the first value because BehaviourSubjects always emit the current value on subscribe. + filter((count) => count === 0) // Wait until all the inflight requests are completed. + ) + ); + this.flush$.next(); + await promise; + } + /** * Shuts down the shipper. * Triggers a flush of the internal queue to attempt to send any events held in the queue. */ public shutdown() { this.shutdown$.next(); + this.flush$.complete(); this.shutdown$.complete(); this.isOptedIn$.complete(); } @@ -226,17 +251,26 @@ export class ElasticV3ServerShipper implements IShipper { takeUntil(this.shutdown$), map(() => ({ shouldFlush: false })) ), + // Whenever a `flush` request comes in + this.flush$.pipe(map(() => ({ shouldFlush: true }))), // Attempt to send one last time on shutdown, flushing the queue this.shutdown$.pipe(map(() => ({ shouldFlush: true }))) ) .pipe( // Only move ahead if it's opted-in and online, and there are some events in the queue - filter( - () => + filter(() => { + const shouldSendAnything = this.isOptedIn$.value === true && this.firstTimeOffline === null && - this.internalQueue.length > 0 - ), + this.internalQueue.length > 0; + + // If it should not send anything, re-emit the inflight request observable just in case it's already 0 + if (!shouldSendAnything) { + this.inFlightRequests$.next(this.inFlightRequests$.value); + } + + return shouldSendAnything; + }), // Send the events: // 1. Set lastBatchSent and retrieve the events to send (clearing the queue) in a synchronous operation to avoid race conditions. @@ -298,6 +332,7 @@ export class ElasticV3ServerShipper implements IShipper { private async sendEvents(events: Event[]) { this.initContext.logger.debug(`Reporting ${events.length} events...`); + this.inFlightRequests$.next(this.inFlightRequests$.value + 1); try { const code = await this.makeRequest(events); this.reportTelemetryCounters(events, { code }); @@ -308,6 +343,7 @@ export class ElasticV3ServerShipper implements IShipper { this.reportTelemetryCounters(events, { code: error.code, error }); this.firstTimeOffline = undefined; } + this.inFlightRequests$.next(Math.max(0, this.inFlightRequests$.value - 1)); } private async makeRequest(events: Event[]): Promise { diff --git a/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts b/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts index 0bf00e91c7d0ed..e60686937884a4 100644 --- a/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts +++ b/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts @@ -135,6 +135,12 @@ export class FullStoryShipper implements IShipper { }); } + /** + * Flushes all internal queues of the shipper. + * It doesn't really do anything inside because this shipper doesn't hold any internal queues. + */ + public async flush() {} + /** * Shuts down the shipper. * It doesn't really do anything inside because this shipper doesn't hold any internal queues. diff --git a/packages/analytics/shippers/gainsight/src/gainsight_shipper.ts b/packages/analytics/shippers/gainsight/src/gainsight_shipper.ts index a12f373fcd388e..157cfaee22f0cf 100644 --- a/packages/analytics/shippers/gainsight/src/gainsight_shipper.ts +++ b/packages/analytics/shippers/gainsight/src/gainsight_shipper.ts @@ -93,6 +93,12 @@ export class GainsightShipper implements IShipper { }); } + /** + * Flushes all internal queues of the shipper. + * It doesn't really do anything inside because this shipper doesn't hold any internal queues. + */ + public async flush() {} + /** * Shuts down the shipper. * It doesn't really do anything inside because this shipper doesn't hold any internal queues. 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 92e1ddaa45cc07..d60530b014be56 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 @@ -51,7 +51,8 @@ const requiredProps: TableListViewProps = { getDetailViewLink: () => 'http://elastic.co', }; -describe('TableListView', () => { +// FLAKY: https://github.com/elastic/kibana/issues/145267 +describe.skip('TableListView', () => { beforeAll(() => { jest.useFakeTimers('legacy'); }); diff --git a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts index 3d98cf43929262..7f32ea7ed41a62 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts @@ -17,6 +17,7 @@ export const analyticsClientMock: jest.Mocked = { removeContextProvider: jest.fn(), registerShipper: jest.fn(), telemetryCounter$: new Subject(), + flush: jest.fn(), shutdown: jest.fn(), }; 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 0dcd49bd69fcc9..60656e9dfd1cb8 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 @@ -45,6 +45,9 @@ export class AnalyticsService { this.registerBrowserInfoAnalyticsContext(); this.subscriptionsHandler.add(trackClicks(this.analyticsClient, core.env.mode.dev)); this.subscriptionsHandler.add(trackViewportSize(this.analyticsClient)); + + // Register a flush method in the browser so CI can explicitly call it before closing the browser. + window.__kbnAnalytics = { flush: () => this.analyticsClient.flush() }; } public setup({ injectedMetadata }: AnalyticsServiceSetupDeps): AnalyticsServiceSetup { @@ -69,9 +72,9 @@ export class AnalyticsService { }; } - public stop() { + public async stop() { this.subscriptionsHandler.unsubscribe(); - this.analyticsClient.shutdown(); + await this.analyticsClient.shutdown(); } /** diff --git a/packages/core/analytics/core-analytics-browser/index.ts b/packages/core/analytics/core-analytics-browser/index.ts index 2484f37bbf563f..331f1695d9f201 100644 --- a/packages/core/analytics/core-analytics-browser/index.ts +++ b/packages/core/analytics/core-analytics-browser/index.ts @@ -6,4 +6,8 @@ * Side Public License, v 1. */ -export type { AnalyticsServiceSetup, AnalyticsServiceStart } from './src/types'; +export type { + AnalyticsServiceSetup, + AnalyticsServiceStart, + KbnAnalyticsWindowApi, +} from './src/types'; diff --git a/packages/core/analytics/core-analytics-browser/src/types.ts b/packages/core/analytics/core-analytics-browser/src/types.ts index e18a5faba8fbc8..dbc35043613cd3 100644 --- a/packages/core/analytics/core-analytics-browser/src/types.ts +++ b/packages/core/analytics/core-analytics-browser/src/types.ts @@ -13,7 +13,7 @@ import type { AnalyticsClient } from '@kbn/analytics-client'; * {@link AnalyticsClient} * @public */ -export type AnalyticsServiceSetup = Omit; +export type AnalyticsServiceSetup = Omit; /** * Exposes the public APIs of the AnalyticsClient during the start phase @@ -24,3 +24,19 @@ export type AnalyticsServiceStart = Pick< AnalyticsClient, 'optIn' | 'reportEvent' | 'telemetryCounter$' >; + +/** + * API exposed through `window.__kbnAnalytics` + */ +export interface KbnAnalyticsWindowApi { + /** + * Returns a promise that resolves when all the events in the queue have been sent. + */ + flush: AnalyticsClient['flush']; +} + +declare global { + interface Window { + __kbnAnalytics: KbnAnalyticsWindowApi; + } +} diff --git a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts index 46b0726660e4ca..141f5b9970c0bf 100644 --- a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts +++ b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts @@ -65,8 +65,8 @@ export class AnalyticsService { }; } - public stop() { - this.analyticsClient.shutdown(); + public async stop() { + await this.analyticsClient.shutdown(); } /** diff --git a/packages/core/analytics/core-analytics-server/src/contracts.ts b/packages/core/analytics/core-analytics-server/src/contracts.ts index 1b297b197374da..4879b988b1752e 100644 --- a/packages/core/analytics/core-analytics-server/src/contracts.ts +++ b/packages/core/analytics/core-analytics-server/src/contracts.ts @@ -13,14 +13,14 @@ import type { AnalyticsClient } from '@kbn/analytics-client'; * {@link AnalyticsClient} * @public */ -export type AnalyticsServicePreboot = Omit; +export type AnalyticsServicePreboot = Omit; /** * Exposes the public APIs of the AnalyticsClient during the setup phase. * {@link AnalyticsClient} * @public */ -export type AnalyticsServiceSetup = Omit; +export type AnalyticsServiceSetup = Omit; /** * Exposes the public APIs of the AnalyticsClient during the start phase diff --git a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts index b642c505cad38b..32245c6e9f61cd 100644 --- a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts +++ b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts @@ -57,6 +57,7 @@ describe('HttpResources service', () => { describe(`${name} register`, () => { const routeConfig: RouteConfig = { path: '/', validate: false }; let register: HttpResources['register']; + beforeEach(async () => { register = await initializer(); }); @@ -81,32 +82,8 @@ describe('HttpResources service', () => { } ); }); - - it('can attach headers, except the CSP header', async () => { - register(routeConfig, async (ctx, req, res) => { - return res.renderCoreApp({ - headers: { - 'content-security-policy': "script-src 'unsafe-eval'", - 'x-kibana': '42', - }, - }); - }); - - const [[, routeHandler]] = router.get.mock.calls; - - const responseFactory = createHttpResourcesResponseFactory(); - await routeHandler(context, kibanaRequest, responseFactory); - - expect(responseFactory.ok).toHaveBeenCalledWith({ - body: '', - headers: { - 'x-kibana': '42', - 'content-security-policy': - "script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - }, - }); - }); }); + describe('renderAnonymousCoreApp', () => { it('formats successful response', async () => { register(routeConfig, async (ctx, req, res) => { @@ -127,32 +104,8 @@ describe('HttpResources service', () => { } ); }); - - it('can attach headers, except the CSP header', async () => { - register(routeConfig, async (ctx, req, res) => { - return res.renderAnonymousCoreApp({ - headers: { - 'content-security-policy': "script-src 'unsafe-eval'", - 'x-kibana': '42', - }, - }); - }); - - const [[, routeHandler]] = router.get.mock.calls; - - const responseFactory = createHttpResourcesResponseFactory(); - await routeHandler(context, kibanaRequest, responseFactory); - - expect(responseFactory.ok).toHaveBeenCalledWith({ - body: '', - headers: { - 'x-kibana': '42', - 'content-security-policy': - "script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - }, - }); - }); }); + describe('renderHtml', () => { it('formats successful response', async () => { const htmlBody = ''; @@ -167,20 +120,17 @@ describe('HttpResources service', () => { body: htmlBody, headers: { 'content-type': 'text/html', - 'content-security-policy': - "script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", }, }); }); - it('can attach headers, except the CSP & "content-type" headers', async () => { + it('can attach headers, except the "content-type" header', async () => { const htmlBody = ''; register(routeConfig, async (ctx, req, res) => { return res.renderHtml({ body: htmlBody, headers: { 'content-type': 'text/html5', - 'content-security-policy': "script-src 'unsafe-eval'", 'x-kibana': '42', }, }); @@ -196,12 +146,11 @@ describe('HttpResources service', () => { headers: { 'content-type': 'text/html', 'x-kibana': '42', - 'content-security-policy': - "script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", }, }); }); }); + describe('renderJs', () => { it('formats successful response', async () => { const jsBody = 'alert(1);'; @@ -216,20 +165,17 @@ describe('HttpResources service', () => { body: jsBody, headers: { 'content-type': 'text/javascript', - 'content-security-policy': - "script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", }, }); }); - it('can attach headers, except the CSP & "content-type" headers', async () => { + it('can attach headers, except the "content-type" header', async () => { const jsBody = 'alert(1);'; register(routeConfig, async (ctx, req, res) => { return res.renderJs({ body: jsBody, headers: { 'content-type': 'text/html', - 'content-security-policy': "script-src 'unsafe-eval'", 'x-kibana': '42', }, }); @@ -245,12 +191,11 @@ describe('HttpResources service', () => { headers: { 'content-type': 'text/javascript', 'x-kibana': '42', - 'content-security-policy': - "script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", }, }); }); }); + describe('renderCss', () => { it('formats successful response', async () => { const cssBody = `body {border: 1px solid red;}`; @@ -265,20 +210,17 @@ describe('HttpResources service', () => { body: cssBody, headers: { 'content-type': 'text/css', - 'content-security-policy': - "script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", }, }); }); - it('can attach headers, except the CSP & "content-type" headers', async () => { + it('can attach headers, except the "content-type" header', async () => { const cssBody = `body {border: 1px solid red;}`; register(routeConfig, async (ctx, req, res) => { return res.renderCss({ body: cssBody, headers: { 'content-type': 'text/css5', - 'content-security-policy': "script-src 'unsafe-eval'", 'x-kibana': '42', }, }); @@ -294,8 +236,6 @@ describe('HttpResources service', () => { headers: { 'content-type': 'text/css', 'x-kibana': '42', - 'content-security-policy': - "script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", }, }); }); diff --git a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts index 22be209158b894..13bd334148ae5e 100644 --- a/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts +++ b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts @@ -101,7 +101,6 @@ export class HttpResourcesService implements CoreService { toolkit = createToolkit(); }); - it('adds the kbn-name header to the response', () => { - const config = createConfig({ name: 'my-server-name' }); + it('adds the kbn-name and Content-Security-Policy headers to the response', () => { + const config = createConfig({ + name: 'my-server-name', + csp: { strict: true, warnLegacyBrowsers: true, disableEmbedding: true, header: 'foo' }, + }); const handler = createCustomHeadersPreResponseHandler(config as HttpConfig); handler({} as any, {} as any, toolkit); expect(toolkit.next).toHaveBeenCalledTimes(1); - expect(toolkit.next).toHaveBeenCalledWith({ headers: { 'kbn-name': 'my-server-name' } }); + expect(toolkit.next).toHaveBeenCalledWith({ + headers: { + 'Content-Security-Policy': 'foo', + 'kbn-name': 'my-server-name', + }, + }); }); it('adds the security headers and custom headers defined in the configuration', () => { const config = createConfig({ name: 'my-server-name', + csp: { strict: true, warnLegacyBrowsers: true, disableEmbedding: true, header: 'foo' }, securityResponseHeaders: { headerA: 'value-A', headerB: 'value-B', // will be overridden by the custom response header below @@ -276,6 +285,7 @@ describe('customHeaders pre-response handler', () => { expect(toolkit.next).toHaveBeenCalledTimes(1); expect(toolkit.next).toHaveBeenCalledWith({ headers: { + 'Content-Security-Policy': 'foo', 'kbn-name': 'my-server-name', headerA: 'value-A', headerB: 'x', @@ -283,11 +293,13 @@ describe('customHeaders pre-response handler', () => { }); }); - it('preserve the kbn-name value from server.name if defined in custom headders ', () => { + it('do not allow overwrite of the kbn-name and Content-Security-Policy headers if defined in custom headders ', () => { const config = createConfig({ name: 'my-server-name', + csp: { strict: true, warnLegacyBrowsers: true, disableEmbedding: true, header: 'foo' }, customResponseHeaders: { 'kbn-name': 'custom-name', + 'Content-Security-Policy': 'custom-csp', headerA: 'value-A', headerB: 'value-B', }, @@ -300,6 +312,7 @@ describe('customHeaders pre-response handler', () => { expect(toolkit.next).toHaveBeenCalledWith({ headers: { 'kbn-name': 'my-server-name', + 'Content-Security-Policy': 'foo', headerA: 'value-A', headerB: 'value-B', }, diff --git a/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts b/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts index 11e034a56914bc..3fe9c8ac727ffe 100644 --- a/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts +++ b/packages/core/http/core-http-server-internal/src/lifecycle_handlers.ts @@ -61,12 +61,18 @@ export const createVersionCheckPostAuthHandler = (kibanaVersion: string): OnPost }; export const createCustomHeadersPreResponseHandler = (config: HttpConfig): OnPreResponseHandler => { - const { name: serverName, securityResponseHeaders, customResponseHeaders } = config; + const { + name: serverName, + securityResponseHeaders, + customResponseHeaders, + csp: { header: cspHeader }, + } = config; return (request, response, toolkit) => { const additionalHeaders = { ...securityResponseHeaders, ...customResponseHeaders, + 'Content-Security-Policy': cspHeader, [KIBANA_NAME_HEADER]: serverName, }; 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 bb260ae23c908a..18e6a21ed2dbaf 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 @@ -26,6 +26,7 @@ const createConfigService = () => { configService.atPath.mockImplementation((path) => { if (path === 'server') { return new BehaviorSubject({ + name: 'kibana', hosts: ['localhost'], maxPayload: new ByteSizeValue(1024), autoListen: true, diff --git a/packages/core/root/core-root-server-internal/src/server.ts b/packages/core/root/core-root-server-internal/src/server.ts index d4ae597c953ffe..4cdfabddf26977 100644 --- a/packages/core/root/core-root-server-internal/src/server.ts +++ b/packages/core/root/core-root-server-internal/src/server.ts @@ -430,7 +430,7 @@ export class Server { public async stop() { this.log.debug('stopping server'); - this.analytics.stop(); + await this.analytics.stop(); await this.http.stop(); // HTTP server has to stop before savedObjects and ES clients are closed to be able to gracefully attempt to resolve any pending requests await this.plugins.stop(); await this.savedObjects.stop(); diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index 49d77b7882f4ae..f93a80f82d06e7 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -772,7 +772,7 @@ exports[`prepares assets for distribution: metrics.json 1`] = ` \\"group\\": \\"page load bundle size\\", \\"id\\": \\"foo\\", \\"value\\": 2457, - \\"limitConfigPath\\": \\"node_modules/@kbn/optimizer/limits.yml\\" + \\"limitConfigPath\\": \\"packages/kbn-optimizer/limits.yml\\" }, { \\"group\\": \\"async chunks size\\", diff --git a/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts b/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts index ac1bcb02a0349e..3e9e25e790b47a 100644 --- a/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts +++ b/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts @@ -79,7 +79,7 @@ export class BundleMetricsPlugin { id: bundle.id, value: entry.size, limit: bundle.pageLoadAssetSizeLimit, - limitConfigPath: `node_modules/@kbn/optimizer/limits.yml`, + limitConfigPath: `packages/kbn-optimizer/limits.yml`, }, { group: `async chunks size`, diff --git a/packages/kbn-rule-data-utils/index.ts b/packages/kbn-rule-data-utils/index.ts index ddf6215aaba900..897e5609a83475 100644 --- a/packages/kbn-rule-data-utils/index.ts +++ b/packages/kbn-rule-data-utils/index.ts @@ -10,3 +10,4 @@ export * from './src/technical_field_names'; export * from './src/alerts_as_data_rbac'; export * from './src/alerts_as_data_severity'; export * from './src/alerts_as_data_status'; +export * from './src/routes/stack_rule_paths'; diff --git a/packages/kbn-rule-data-utils/src/routes/stack_rule_paths.ts b/packages/kbn-rule-data-utils/src/routes/stack_rule_paths.ts new file mode 100644 index 00000000000000..49ba239829b24f --- /dev/null +++ b/packages/kbn-rule-data-utils/src/routes/stack_rule_paths.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 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 ruleDetailsRoute = '/rule/:ruleId' as const; +export const triggersActionsRoute = '/app/management/insightsAndAlerting/triggersActions' as const; + +export const getRuleDetailsRoute = (ruleId: string) => ruleDetailsRoute.replace(':ruleId', ruleId); diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index ae37273c8aefb5..6b51906cca1ef0 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -43,6 +43,13 @@ const ALERT_UUID = `${ALERT_NAMESPACE}.uuid` as const; const ALERT_WORKFLOW_REASON = `${ALERT_NAMESPACE}.workflow_reason` as const; const ALERT_WORKFLOW_STATUS = `${ALERT_NAMESPACE}.workflow_status` as const; const ALERT_WORKFLOW_USER = `${ALERT_NAMESPACE}.workflow_user` as const; +const ALERT_SUPPRESSION_META = `${ALERT_NAMESPACE}.suppression` as const; +const ALERT_SUPPRESSION_TERMS = `${ALERT_SUPPRESSION_META}.terms` as const; +const ALERT_SUPPRESSION_FIELD = `${ALERT_SUPPRESSION_TERMS}.field` as const; +const ALERT_SUPPRESSION_VALUE = `${ALERT_SUPPRESSION_TERMS}.value` as const; +const ALERT_SUPPRESSION_START = `${ALERT_SUPPRESSION_META}.start` as const; +const ALERT_SUPPRESSION_END = `${ALERT_SUPPRESSION_META}.end` as const; +const ALERT_SUPPRESSION_DOCS_COUNT = `${ALERT_SUPPRESSION_META}.docs_count` as const; // Fields pertaining to the rule associated with the alert const ALERT_RULE_AUTHOR = `${ALERT_RULE_NAMESPACE}.author` as const; @@ -167,6 +174,12 @@ const fields = { ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_ID, ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_NAME, ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_REFERENCE, + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_FIELD, + ALERT_SUPPRESSION_VALUE, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_DOCS_COUNT, SPACE_IDS, VERSION, }; @@ -236,6 +249,12 @@ export { ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_ID, ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_NAME, ALERT_THREAT_TECHNIQUE_SUBTECHNIQUE_REFERENCE, + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_FIELD, + ALERT_SUPPRESSION_VALUE, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_DOCS_COUNT, TAGS, TIMESTAMP, SPACE_IDS, diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts index ad84d295cff845..63711c8a036bd1 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts @@ -44,6 +44,7 @@ export interface UseExceptionListsProps { notifications: NotificationsStart; initialPagination?: Pagination; hideLists?: readonly string[]; + initialSort?: Sort; } export interface UseExceptionListProps { @@ -56,6 +57,7 @@ export interface UseExceptionListProps { showEndpointListsOnly: boolean; matchFilters: boolean; onSuccess?: (arg: UseExceptionListItemsSuccess) => void; + sort?: Sort; } export interface FilterExceptionsOptions { @@ -81,6 +83,10 @@ export interface ApiListExportProps { onSuccess: (blob: Blob) => void; } +export interface Sort { + field: string; + order: string; +} export interface Pagination { page: Page; perPage: PerPage; @@ -168,6 +174,7 @@ export interface ApiCallFetchExceptionListsProps { http: HttpStart; namespaceTypes: string; pagination: Partial; + sort?: Sort; filters: string; signal: AbortSignal; } diff --git a/packages/kbn-securitysolution-list-api/src/api/index.ts b/packages/kbn-securitysolution-list-api/src/api/index.ts index 8d23db33601a47..440217ff65167e 100644 --- a/packages/kbn-securitysolution-list-api/src/api/index.ts +++ b/packages/kbn-securitysolution-list-api/src/api/index.ts @@ -231,14 +231,15 @@ const fetchExceptionLists = async ({ namespaceTypes, pagination, signal, + sort, }: ApiCallFetchExceptionListsProps): Promise => { const query = { filter: filters || undefined, namespace_type: namespaceTypes, page: pagination.page ? `${pagination.page}` : '1', per_page: pagination.perPage ? `${pagination.perPage}` : '20', - sort_field: 'exception-list.created_at', - sort_order: 'desc', + sort_field: sort?.field ? sort?.field : 'exception-list.created_at', + sort_order: sort?.order ? sort?.order : 'desc', }; return http.fetch(`${EXCEPTION_LIST_URL}/_find`, { @@ -254,6 +255,7 @@ const fetchExceptionListsWithValidation = async ({ namespaceTypes, pagination, signal, + sort, }: ApiCallFetchExceptionListsProps): Promise => flow( () => @@ -265,6 +267,7 @@ const fetchExceptionListsWithValidation = async ({ namespaceTypes, pagination, signal, + sort, }), toError ), diff --git a/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts index c73405f1950b82..876b236004a77c 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts @@ -11,6 +11,7 @@ import type { ExceptionListSchema, UseExceptionListsProps, Pagination, + Sort, } from '@kbn/securitysolution-io-ts-list-types'; import { fetchExceptionLists } from '@kbn/securitysolution-list-api'; @@ -22,7 +23,9 @@ export type ReturnExceptionLists = [ exceptionLists: ExceptionListSchema[], pagination: Pagination, setPagination: React.Dispatch>, - fetchLists: Func | null + fetchLists: Func | null, + sort: Sort, + setSort: React.Dispatch> ]; const DEFAULT_PAGINATION = { @@ -31,6 +34,11 @@ const DEFAULT_PAGINATION = { total: 0, }; +const DEFAULT_SORT = { + field: 'created_at', + order: 'desc', +}; + /** * Hook for fetching ExceptionLists * @@ -51,9 +59,11 @@ export const useExceptionLists = ({ namespaceTypes, notifications, hideLists = [], + initialSort = DEFAULT_SORT, }: UseExceptionListsProps): ReturnExceptionLists => { const [exceptionLists, setExceptionLists] = useState([]); const [pagination, setPagination] = useState(initialPagination); + const [sort, setSort] = useState(initialSort); const [loading, setLoading] = useState(true); const abortCtrlRef = useRef(); @@ -87,6 +97,7 @@ export const useExceptionLists = ({ page: pagination.page, perPage: pagination.perPage, }, + sort, signal: abortCtrlRef.current.signal, }); @@ -115,6 +126,7 @@ export const useExceptionLists = ({ notifications.toasts, pagination.page, pagination.perPage, + sort, ]); useEffect(() => { @@ -125,5 +137,5 @@ export const useExceptionLists = ({ }; }, [fetchData]); - return [loading, exceptionLists, pagination, setPagination, fetchData]; + return [loading, exceptionLists, pagination, setPagination, fetchData, sort, setSort]; }; diff --git a/src/core/server/integration_tests/http/lifecycle.test.ts b/src/core/server/integration_tests/http/lifecycle.test.ts index 2833d9f0bad138..239350747c7fd6 100644 --- a/src/core/server/integration_tests/http/lifecycle.test.ts +++ b/src/core/server/integration_tests/http/lifecycle.test.ts @@ -1473,6 +1473,32 @@ describe('OnPreResponse', () => { }); }); +describe('runs with default preResponse handlers', () => { + it('does not allow overwriting of the "kbn-name" and "Content-Security-Policy" headers', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => + res.ok({ + headers: { + foo: 'bar', + 'kbn-name': 'hijacked!', + 'Content-Security-Policy': 'hijacked!', + }, + }) + ); + await server.start(); + + const response = await supertest(innerServer.listener).get('/').expect(200); + + expect(response.header.foo).toBe('bar'); + expect(response.header['kbn-name']).toBe('kibana'); + expect(response.header['content-security-policy']).toBe( + `script-src 'self' 'unsafe-eval'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'` + ); + }); +}); + describe('run interceptors in the right order', () => { it('with Auth registered', async () => { const { diff --git a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts index 12f58518952f35..33baf258a13d26 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts @@ -80,7 +80,7 @@ describe('checking migration metadata changes on all registered SO types', () => "endpoint:user-artifact": "f94c250a52b30d0a2d32635f8b4c5bdabd1e25c0", "endpoint:user-artifact-manifest": "8c14d49a385d5d1307d956aa743ec78de0b2be88", "enterprise_search_telemetry": "fafcc8318528d34f721c42d1270787c52565bad5", - "epm-packages": "cb22b422398a785e7e0565a19c6d4d5c7af6f2fd", + "epm-packages": "fe3716a54188b3c71327fa060dd6780a674d3994", "epm-packages-assets": "9fd3d6726ac77369249e9a973902c2cd615fc771", "event_loop_delays_daily": "d2ed39cf669577d90921c176499908b4943fb7bd", "exception-list": "fe8cc004fd2742177cdb9300f4a67689463faf9c", diff --git a/src/core/server/integration_tests/saved_objects/migrations/multiple_kibana_nodes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/multiple_kibana_nodes.test.ts index e6d74c28efb8ca..73d22b8aba5a9a 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/multiple_kibana_nodes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/multiple_kibana_nodes.test.ts @@ -99,6 +99,9 @@ async function createRoot({ logFileName }: CreateRootConfig) { return root; } +// suite is very long, the 10mins default can cause timeouts +jest.setTimeout(15 * 60 * 1000); + describe('migration v2', () => { let esServer: kbnTestServer.TestElasticsearchUtils; let rootA: Root; @@ -121,9 +124,7 @@ describe('migration v2', () => { }, }; - afterAll(async () => { - await new Promise((resolve) => setTimeout(resolve, 10000)); - }); + const delay = (timeInMs: number) => new Promise((resolve) => setTimeout(resolve, timeInMs)); beforeEach(async () => { await removeLogFiles(); @@ -161,10 +162,10 @@ describe('migration v2', () => { if (esServer) { await esServer.stop(); + await delay(10000); } }); - const delay = (timeInMs: number) => new Promise((resolve) => setTimeout(resolve, timeInMs)); const startWithDelay = async (instances: Root[], delayInSec: number) => { const promises: Array> = []; for (let i = 0; i < instances.length; i++) { diff --git a/src/dev/typescript/project.ts b/src/dev/typescript/project.ts index 32245e26c69ec1..c148cccfa73512 100644 --- a/src/dev/typescript/project.ts +++ b/src/dev/typescript/project.ts @@ -174,4 +174,8 @@ export class Project { ? [this.tsConfigPath, ...this.baseProject.getConfigPaths()] : [this.tsConfigPath]; } + + public getProjectsDeep(): Project[] { + return this.baseProject ? [this, ...this.baseProject.getProjectsDeep()] : [this]; + } } diff --git a/src/dev/typescript/run_check_ts_projects_cli.ts b/src/dev/typescript/run_check_ts_projects_cli.ts index 9156c52a23d019..c4998e67919575 100644 --- a/src/dev/typescript/run_check_ts_projects_cli.ts +++ b/src/dev/typescript/run_check_ts_projects_cli.ts @@ -12,6 +12,7 @@ import { run } from '@kbn/dev-cli-runner'; import { asyncMapWithLimit } from '@kbn/std'; import { createFailError } from '@kbn/dev-cli-errors'; import { getRepoFiles } from '@kbn/get-repo-files'; +import { REPO_ROOT } from '@kbn/utils'; import globby from 'globby'; import { File } from '../file'; @@ -37,6 +38,25 @@ export async function runCheckTsProjectsCli() { const stats = new Stats(); let failed = false; + const everyProjectDeep = new Set(PROJECTS.flatMap((p) => p.getProjectsDeep())); + for (const proj of everyProjectDeep) { + const [, ...baseConfigRels] = proj.getConfigPaths().map((p) => Path.relative(REPO_ROOT, p)); + const configRel = Path.relative(REPO_ROOT, proj.tsConfigPath); + + if (baseConfigRels[0] === 'tsconfig.json') { + failed = true; + log.error( + `[${configRel}]: This tsconfig extends the root tsconfig.json file and shouldn't. The root tsconfig.json file is not a valid base config, you probably want to point to the tsconfig.base.json file.` + ); + } + if (configRel !== 'tsconfig.base.json' && !baseConfigRels.includes('tsconfig.base.json')) { + failed = true; + log.error( + `[${configRel}]: This tsconfig does not extend the tsconfig.base.json file either directly or indirectly. The TS config setup for the repo expects every tsconfig file to extend this base config file.` + ); + } + } + const pathsAndProjects = await asyncMapWithLimit(PROJECTS, 5, async (proj) => { const paths = await globby(proj.getIncludePatterns(), { ignore: proj.getExcludePatterns(), diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/__snapshots__/gauge_component.test.tsx.snap b/src/plugins/chart_expressions/expression_gauge/public/components/__snapshots__/gauge_component.test.tsx.snap index 6f08439797e884..30fb883c9d15fa 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/__snapshots__/gauge_component.test.tsx.snap +++ b/src/plugins/chart_expressions/expression_gauge/public/components/__snapshots__/gauge_component.test.tsx.snap @@ -6,6 +6,7 @@ exports[`GaugeComponent renders the chart 1`] = ` > = memo( noResults={} debugState={window._echDebugStateFlag ?? false} theme={[{ background: { color: 'transparent' } }, chartTheme]} + baseTheme={chartsThemeService.useChartsBaseTheme()} ariaLabel={args.ariaLabel} ariaUseDefaultSummary={!args.ariaLabel} onRenderChange={onRenderChange} diff --git a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx index a497a629553fa5..ce7b158f1908b7 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx @@ -589,6 +589,7 @@ export const HeatmapComponent: FC = memo( debugState={window._echDebugStateFlag ?? false} tooltip={tooltip} theme={[themeOverrides, chartTheme]} + baseTheme={chartsThemeService.useChartsBaseTheme()} xDomain={{ min: dateHistogramMeta && dateHistogramMeta.timeRange diff --git a/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx b/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx index e8883ad16935bb..20cf4d48a08eff 100644 --- a/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx +++ b/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx @@ -336,9 +336,9 @@ export const MetricVis = ({ const scrollContainerRef = useRef(null); const scrollDimensions = useResizeObserver(scrollContainerRef.current); - const { - metric: { minHeight }, - } = getThemeService().useChartsBaseTheme(); + const baseTheme = getThemeService().useChartsBaseTheme(); + + const minHeight = chartTheme.metric?.minHeight ?? baseTheme.metric.minHeight; useEffect(() => { const minimumRequiredVerticalSpace = minHeight * grid.length; @@ -377,6 +377,7 @@ export const MetricVis = ({ }, chartTheme, ]} + baseTheme={baseTheme} onRenderChange={onRenderChange} onElementClick={(events) => { if (!filterable) { diff --git a/src/plugins/controls/public/time_slider/components/index.scss b/src/plugins/controls/public/time_slider/components/index.scss index 1fe17385b69589..20dfd86a23294e 100644 --- a/src/plugins/controls/public/time_slider/components/index.scss +++ b/src/plugins/controls/public/time_slider/components/index.scss @@ -15,6 +15,10 @@ min-width: $euiSizeXXL * 15; } +.timeSlider-playToggle { + background-color: $euiColorPrimary !important; +} + .timeSlider__anchor { text-decoration: none; width: 100%; diff --git a/src/plugins/controls/public/time_slider/components/time_slider_prepend.tsx b/src/plugins/controls/public/time_slider/components/time_slider_prepend.tsx index cff2007b666ba5..3f569fe6b60be2 100644 --- a/src/plugins/controls/public/time_slider/components/time_slider_prepend.tsx +++ b/src/plugins/controls/public/time_slider/components/time_slider_prepend.tsx @@ -85,6 +85,7 @@ export const TimeSliderPrepend: FC = (props: Props) => { /> {props.waitForControlOutputConsumersToLoad$ === undefined ? null : ( - + + + , dom diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index dff5ff80bcc273..cf2d662aab42de 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -17,8 +17,7 @@ import { import { ViewMode } from '@kbn/embeddable-plugin/public'; import { withSuspense } from '@kbn/presentation-util-plugin/public'; import { context } from '@kbn/kibana-react-plugin/public'; -import { ExitFullScreenButton as ExitFullScreenButtonUi } from '@kbn/kibana-react-plugin/public'; -import { CoreStart } from '@kbn/core/public'; +import { ExitFullScreenButton } from '@kbn/shared-ux-button-exit-full-screen'; import { DashboardContainer, DashboardLoadedInfo } from '../dashboard_container'; import { DashboardGrid } from '../grid'; @@ -106,7 +105,6 @@ export class DashboardViewport extends React.Component {isFullScreenMode && ( - // TODO: Replace with Shared UX ExitFullScreenButton once https://github.com/elastic/kibana/issues/140311 is resolved - )} diff --git a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx index 742342123140f2..5bf395c6fdb8ce 100644 --- a/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx +++ b/src/plugins/discover/public/application/main/components/field_stats_table/field_stats_table.tsx @@ -24,6 +24,24 @@ import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { FIELD_STATISTICS_LOADED } from './constants'; import type { GetStateReturn } from '../../services/discover_state'; import { AvailableFields$, DataRefetch$, DataTotalHits$ } from '../../hooks/use_saved_search'; +export interface RandomSamplingOption { + mode: 'random_sampling'; + seed: string; + probability: number; +} + +export interface NormalSamplingOption { + mode: 'normal_sampling'; + seed: string; + shardSize: number; +} + +export interface NoSamplingOption { + mode: 'no_sampling'; + seed: string; +} + +export type SamplingOption = RandomSamplingOption | NormalSamplingOption | NoSamplingOption; export interface DataVisualizerGridEmbeddableInput extends EmbeddableInput { dataView: DataView; @@ -39,6 +57,7 @@ export interface DataVisualizerGridEmbeddableInput extends EmbeddableInput { sessionId?: string; fieldsToFetch?: string[]; totalDocuments?: number; + samplingOption?: SamplingOption; } export interface DataVisualizerGridEmbeddableOutput extends EmbeddableOutput { showDistributions?: boolean; @@ -163,6 +182,11 @@ export const FieldStatisticsTable = (props: FieldStatisticsTableProps) => { totalDocuments: savedSearchDataTotalHits$ ? savedSearchDataTotalHits$.getValue()?.result : undefined, + samplingOption: { + mode: 'normal_sampling', + shardSize: 5000, + seed: searchSessionId, + } as NormalSamplingOption, }); embeddable.reload(); } diff --git a/src/plugins/discover/public/utils/get_fields_to_show.ts b/src/plugins/discover/public/utils/get_fields_to_show.ts index fd9fdd70a42790..be6bcc2ea5ce94 100644 --- a/src/plugins/discover/public/utils/get_fields_to_show.ts +++ b/src/plugins/discover/public/utils/get_fields_to_show.ts @@ -8,24 +8,38 @@ import { getFieldSubtypeMulti } from '@kbn/data-views-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; +/** + * Returns am array of fields to display in the Documents column of the data table + * If showMultiFields is set to false, it filters out multifields that have a parent, to prevent entries for multifields + * like this: field, field.keyword, field.whatever + * @param fields + * @param dataView + * @param showMultiFields + */ export const getFieldsToShow = (fields: string[], dataView: DataView, showMultiFields: boolean) => { - const childParentFieldsMap = {} as Record; - const mapping = (name: string) => dataView.fields.getByName(name); + if (showMultiFields) { + return fields; + } + const fieldSet = new Set(); + const childParentFieldsMap = new Map(); + const parentFieldSet = new Set(); fields.forEach((key) => { - const mapped = mapping(key); + const mapped = dataView.fields.getByName(key); const subTypeMulti = mapped && getFieldSubtypeMulti(mapped.spec); + const isMultiField = Boolean(subTypeMulti?.multi); if (mapped && subTypeMulti?.multi?.parent) { - childParentFieldsMap[mapped.name] = subTypeMulti.multi.parent; + childParentFieldsMap.set(key, subTypeMulti.multi.parent); + } + if (mapped && isMultiField) { + parentFieldSet.add(key); } + fieldSet.add(key); }); return fields.filter((key: string) => { - const fieldMapping = mapping(key); - const subTypeMulti = fieldMapping && getFieldSubtypeMulti(fieldMapping.spec); - const isMultiField = !!subTypeMulti?.multi; - if (!isMultiField) { + if (!parentFieldSet.has(key)) { return true; } - const parent = childParentFieldsMap[key]; - return showMultiFields || (parent && !fields.includes(parent)); + const parent = childParentFieldsMap.get(key); + return parent && !fieldSet.has(parent); }); }; diff --git a/src/plugins/files/README.md b/src/plugins/files/README.md index baaea2ecb44cd4..77e771c6b4d0b4 100755 --- a/src/plugins/files/README.md +++ b/src/plugins/files/README.md @@ -1,13 +1,43 @@ -# files +# Files -File upload, download, sharing, and serving over HTTP implementation in Kibana. +The files service provides functionality to manage, retrieve, share files in Kibana. -## Status +## Overview -The files plugin is currently still under development. If you want to use files please reach out to the (App Services team)[https://github.com/orgs/elastic/teams/kibana-app-services] and let's talk about your use case and share our current roadmap. +The file service provides the following capabilities to plugins to create, +manage and share file contents: ---- +* Auto-register HTTP APIs for your file + * These HTTP APIs can also be access controlled +* A server-side client +* A browser-side client +* Various reusable UI components that can provide a consistent user experience +* Publicly sharing files (i.e., bypassing all security) +* Leveraging file caching where possible + +## Getting started + +See [the tutorial](https://docs.elastic.dev/kibana-dev-docs/file-service). + +## API reference + +See the [reference](https://docs.elastic.dev/kibana-dev-docs/api/files). + +## Public components +> To see any component in action run `yarn storybook files` and follow the prompts + +The files service offers a number of UI components that are reusable UIs to provide +a consistent UX for managing files while keeping integration with the file service +light for consumers. + +* `` - A specialized component for efficiently downloading and rendering files in the UI that wraps an `img` tag. +* `` - The `EuiFilePicker` wrapped with robust upload logic for one or multiple files +* `` - A way for users to view and select from one or more uploaded files + +in the terminal. + +--- ## Development See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/files/docs/tutorial.mdx b/src/plugins/files/docs/tutorial.mdx index 3b2853ff65b112..66857db2473cb2 100644 --- a/src/plugins/files/docs/tutorial.mdx +++ b/src/plugins/files/docs/tutorial.mdx @@ -53,7 +53,9 @@ Consumers of the file service are able to decide what metadata they want to asso ### How to set up files for your plugin -All setup examples are based on the [`filesExample` plugin](https://github.com/elastic/kibana/blob/d431e87f7ff43833bff085e5bb5b2ab603bfa05d/examples/files_example/README.md). +All setup examples are based on the [`filesExample` plugin](https://github.com/elastic/kibana/blob/b6693bd9260c1620ec5ad8f09141b534c3b02e81/examples/files_example/README.md). + +#### Prepare your plugin First add the `files` plugin as a required dependency in your `kibana.json`: @@ -61,7 +63,10 @@ First add the `files` plugin as a required dependency in your `kibana.json`: "requiredPlugins": ["files"], ```
-Next define your file kinds. Your plugin can have more than one file kind. Each file kind should represent a specific use case, for example: an image for user avatars. + +#### Declare your file kind + +Your plugin can have more than one file kind. Each file kind should represent a specific use case, for example: an image for user avatars. ```ts import { FileKind } from '@kbn/files-plugin/common'; @@ -93,23 +98,65 @@ In this example we have chosen to disallow metadata updates and file sharing end HTTP access tags (`access:myApp`) define a new permission for an HTTP endpoint. **Ensure that users have the permissions they need to access their files!** See `KibanaFeatureConfig` for more information. +#### Register the file kind + Now we are able to register this file kind with the file service: ```ts - // in your server-side plugin code + // in your server-side and client-side plugin code public setup(core: CoreSetup, { files }: { files: FilesSetup }) { files.registerFileKind(exampleFileKind); } ``` -You are now able to access your files both from the server-side and the public-side of your plugin. For example, you can now list all of the files of a file kind by doing the following: +#### Use the file client + +Let's use the file client now that we have all the boilerplate out of the way: ```ts // in your public code - const result = await files.filesClientFactory.asScoped(exampleFileKind.id).list(); + const client = files.filesClientFactory.asScoped('filesExample'); // Create a file client that is scoped to "filesExample" + const result = await client.list(); +``` + +**Note**: the server-side file client has unrestricted access to all files because it does +not need to pass through HTTP API access control! + +Right now this list will be empty... How do we get our image avatars into the file service? By asking users! + +The file service provides a set of React UI components for you to leverage. Users can give you a new file (`UploadFile`) or select a from a set of files they have already uploaded (`FilePicker`). + +Let's go with the latter for now because users can pick from previously uploaded +avatars or provide a new one. + +```tsx + const client = files.filesClientFactory.asUnscoped(); // Create a client not scoped to a file kind + ... + + + + ... ``` -To create a new file from the browser you can do the following: +You'll notice the `FilesContext` component was introduced here. This provides +access to our registry of file kinds among other things so make sure your app is +wrapped in this context. + +At this point you can use the public components provided by the file service +to render an image in the browser: + +```tsx + const client = files.filesClientFactory.asUnscoped(); // Create a client not scoped to a file kind + ... + ', kind: 'filesExample' })} alt="..." /> + ... +``` + + + To see the React components currently available in action run `yarn storybook files` and follow the prompts in the terminal. + + +Alternatively, you can use the file client directly to create a new file from the browser: ```ts const { file } = await files.example.create({ @@ -126,23 +173,12 @@ To create a new file from the browser you can do the following: await files.example.upload({ id: file.id, body: blob }); ``` -Now display your file to the world using the client side API: - -```tsx -const { files: [file] } = await fileClient.find({ meta: { myValue: 'test' } }); - -{file.alt -``` - -### Public components - - - The file service will expose a number of front-end components to ensure that a consistent UX is provided for. - - Consumers of the file service will have access to lower-level APIs but it is highly recommended to use the existing UI components or to first reach out to the App Services team before building your own UI, others could also benefit from any UI improvements! - +## Recap +After completing this tutorial you should have successfully integrated your application +with the file service: +* Have a file kind setup for your use case +* Use the file client on server or client side to interact with files +* Leverage existing file service UI components to greatly reduce the amount + of code needed to integrate your solution and provide a consistent UX \ No newline at end of file diff --git a/src/plugins/guided_onboarding/README.md b/src/plugins/guided_onboarding/README.md index d54926da3c971a..ce54f42c358ed4 100755 --- a/src/plugins/guided_onboarding/README.md +++ b/src/plugins/guided_onboarding/README.md @@ -2,7 +2,7 @@ This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages. -The guided onboarding plugin includes a client-side code for the UI and the server side code for the internal API. The server-side code is not intended for external use. +The guided onboarding plugin includes a client-side code for the UI and the server-side code for the internal API. The server-side code is not intended for external use. The client-side code registers a button in the Kibana header that controls the guided onboarding panel (checklist) depending on the current state. There is also an API service exposed from the client-side start contract. The API service is intended for external use by other plugins. @@ -10,9 +10,11 @@ The client-side code registers a button in the Kibana header that controls the g ## Development -1. Start Kibana with examples `yarn start --run-examples` to be able to see the guidedOnboardingExample plugin. +1. Guided onboarding is only enabled on cloud. Update your `kibana.dev.yml` file with `xpack.cloud.id: 'testID'` to imitate the Cloud environment. -2. Navigate to `/app/guidedOnboardingExample` to start a guide and check the button in the header. +2. Start Kibana with the example plugins enabled: `yarn start --run-examples`. + +3. Navigate to `/app/home#/getting_started` to view the onboarding landing page and start a guide. Alternatively, you can also start a guide within the guided onboarding example plugin at `/app/guidedOnboardingExample`. The example plugin includes a sample guide that showcases the framework's capabilities. It also provides a form to dynamically start a guide at a specific step. ## API service *Also see `KIBANA_FOLDER/examples/guided_onboarding_example` for code examples.* @@ -71,5 +73,3 @@ await guidedOnboardingApi?.completeGuideStep('security', 'add_data'); ## Guides config To use the API service, you need to know a guide ID (one of `search`, `observability`, `security`) and a step ID (for example, `add_data`, `search_experience`, `rules` etc). Refer to guides config files in the folder `./public/constants` for more information. - - diff --git a/src/plugins/guided_onboarding/public/components/guide_button.tsx b/src/plugins/guided_onboarding/public/components/guide_button.tsx index c4b1458a1323b4..ec2e07a7964cca 100644 --- a/src/plugins/guided_onboarding/public/components/guide_button.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_button.tsx @@ -46,8 +46,8 @@ export const GuideButton = ({ isGuidePanelOpen, navigateToLandingPage, }: GuideButtonProps) => { - // TODO handle loading, error state - // https://github.com/elastic/kibana/issues/139799, https://github.com/elastic/kibana/issues/139798 + // TODO handle loading state + // https://github.com/elastic/kibana/issues/139799 // if there is no active guide if (!pluginState || !pluginState.activeGuide || !pluginState.activeGuide.isActive) { 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 e9576b5c61da2e..81c884d8ded0b6 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -10,6 +10,7 @@ import { act } from 'react-dom/test-utils'; import React from 'react'; import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; import { httpServiceMock } from '@kbn/core/public/mocks'; import type { HttpSetup } from '@kbn/core/public'; import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; @@ -30,6 +31,7 @@ import { import { GuidePanel } from './guide_panel'; const applicationMock = applicationServiceMock.createStartContract(); +const notificationsMock = notificationServiceMock.createStartContract(); const setupComponentWithPluginStateMock = async ( httpClient: jest.Mocked, @@ -44,7 +46,9 @@ const setupComponentWithPluginStateMock = async ( const setupGuidePanelComponent = async (api: GuidedOnboardingApi) => { let testBed: TestBed; - const GuidePanelComponent = () => ; + const GuidePanelComponent = () => ( + + ); await act(async () => { testBed = registerTestBed(GuidePanelComponent)(); }); @@ -425,6 +429,12 @@ describe('Guided setup', () => { describe('Quit guide modal', () => { beforeEach(async () => { + httpClient.put.mockResolvedValueOnce({ + pluginState: { + status: 'quit', + isActivePeriod: true, + }, + }); testBed = await setupComponentWithPluginStateMock(httpClient, { status: 'in_progress', isActivePeriod: true, @@ -457,12 +467,6 @@ describe('Guided setup', () => { }); test('cancels out of the quit guide confirmation modal', async () => { - httpClient.put.mockResolvedValueOnce({ - pluginState: { - status: 'quit', - isActivePeriod: true, - }, - }); const { component, find, exists } = testBed; await act(async () => { diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx index 2f62b80fca852f..a31083a384a266 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -28,7 +28,7 @@ import { import { i18n } from '@kbn/i18n'; -import { ApplicationStart } from '@kbn/core/public'; +import { ApplicationStart, NotificationsStart } from '@kbn/core/public'; import type { GuideState, GuideStep as GuideStepStatus } from '@kbn/guided-onboarding'; import { GuideId } from '@kbn/guided-onboarding'; @@ -45,6 +45,7 @@ import { GuideButton } from './guide_button'; interface GuidePanelProps { api: GuidedOnboardingApi; application: ApplicationStart; + notifications: NotificationsStart; } const getProgress = (state?: GuideState): number => { @@ -73,7 +74,7 @@ const getTelemetryGuideId = (guideId?: GuideId) => { } }; -export const GuidePanel = ({ api, application }: GuidePanelProps) => { +export const GuidePanel = ({ api, application, notifications }: GuidePanelProps) => { const { euiTheme } = useEuiTheme(); const [isGuideOpen, setIsGuideOpen] = useState(false); const [isQuitGuideModalOpen, setIsQuitGuideModalOpen] = useState(false); @@ -90,22 +91,31 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { const { id, status } = step; const guideId: GuideId = pluginState!.activeGuide!.guideId!; - if (status === 'ready_to_complete') { - return await api.completeGuideStep(guideId, id); - } + try { + if (status === 'ready_to_complete') { + return await api.completeGuideStep(guideId, id); + } - if (status === 'active' || status === 'in_progress') { - await api.startGuideStep(guideId, id); + if (status === 'active' || status === 'in_progress') { + await api.startGuideStep(guideId, id); - if (stepConfig.location) { - await application.navigateToApp(stepConfig.location.appID, { - path: stepConfig.location.path, - }); + if (stepConfig.location) { + await application.navigateToApp(stepConfig.location.appID, { + path: stepConfig.location.path, + }); - if (stepConfig.manualCompletion?.readyToCompleteOnNavigation) { - await api.completeGuideStep(guideId, id); + if (stepConfig.manualCompletion?.readyToCompleteOnNavigation) { + await api.completeGuideStep(guideId, id); + } } } + } catch (error) { + notifications.toasts.addDanger({ + title: i18n.translate('guidedOnboarding.dropdownPanel.stepHandlerError', { + defaultMessage: 'Unable to update the guide. Please try again later.', + }), + text: error.message, + }); } } }; @@ -118,11 +128,20 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { const completeGuide = async ( completedGuideRedirectLocation: GuideConfig['completedGuideRedirectLocation'] ) => { - await api.completeGuide(pluginState!.activeGuide!.guideId!); + try { + await api.completeGuide(pluginState!.activeGuide!.guideId!); - if (completedGuideRedirectLocation) { - const { appID, path } = completedGuideRedirectLocation; - application.navigateToApp(appID, { path }); + if (completedGuideRedirectLocation) { + const { appID, path } = completedGuideRedirectLocation; + application.navigateToApp(appID, { path }); + } + } catch (error) { + notifications.toasts.addDanger({ + title: i18n.translate('guidedOnboarding.dropdownPanel.completeGuideError', { + defaultMessage: 'Unable to update the guide. Please try again later.', + }), + text: error.message, + }); } }; @@ -153,8 +172,8 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { const guideConfig = getGuideConfig(pluginState?.activeGuide?.guideId)!; - // TODO handle loading, error state - // https://github.com/elastic/kibana/issues/139799, https://github.com/elastic/kibana/issues/139798 + // TODO handle loading state + // https://github.com/elastic/kibana/issues/139799 const stepsCompleted = getProgress(pluginState?.activeGuide); const isGuideReadyToComplete = pluginState?.activeGuide?.status === 'ready_to_complete'; @@ -374,6 +393,7 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { closeModal={closeQuitGuideModal} currentGuide={pluginState!.activeGuide!} telemetryGuideId={telemetryGuideId!} + notifications={notifications} /> )} diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx index 7325ffb6114d1f..e47d3e09e6e312 100644 --- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx +++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx @@ -19,25 +19,38 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { GuideState } from '@kbn/guided-onboarding'; +import { NotificationsStart } from '@kbn/core/public'; import { apiService } from '../services/api'; interface QuitGuideModalProps { closeModal: () => void; currentGuide: GuideState; telemetryGuideId: string; + notifications: NotificationsStart; } export const QuitGuideModal = ({ closeModal, currentGuide, telemetryGuideId, + notifications, }: QuitGuideModalProps) => { - const [isDeleting, setIsDeleting] = useState(false); + const [isLoading, setIsLoading] = useState(false); - const deleteGuide = async () => { - setIsDeleting(true); - await apiService.deactivateGuide(currentGuide); - closeModal(); + const deactivateGuide = async () => { + try { + setIsLoading(true); + await apiService.deactivateGuide(currentGuide); + closeModal(); + } catch (error) { + setIsLoading(false); + notifications.toasts.addDanger({ + title: i18n.translate('guidedOnboarding.quitGuideModal.deactivateGuideError', { + defaultMessage: 'Unable to update the guide. Please try again later.', + }), + text: error.message, + }); + } }; return ( @@ -77,8 +90,8 @@ export const QuitGuideModal = ({ diff --git a/src/plugins/guided_onboarding/public/plugin.tsx b/src/plugins/guided_onboarding/public/plugin.tsx index d4c6e6892c4322..dee46bfb9b3827 100755 --- a/src/plugins/guided_onboarding/public/plugin.tsx +++ b/src/plugins/guided_onboarding/public/plugin.tsx @@ -10,7 +10,14 @@ 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 } from '@kbn/core/public'; +import { + CoreSetup, + CoreStart, + Plugin, + CoreTheme, + ApplicationStart, + NotificationsStart, +} from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import type { @@ -33,7 +40,7 @@ export class GuidedOnboardingPlugin core: CoreStart, { cloud }: AppPluginStartDependencies ): GuidedOnboardingPluginStart { - const { chrome, http, theme, application } = core; + const { chrome, http, theme, application, notifications } = core; // Initialize services apiService.setup(http, !!cloud?.isCloudEnabled); @@ -48,6 +55,7 @@ export class GuidedOnboardingPlugin theme$: theme.theme$, api: apiService, application, + notifications, }), }); } @@ -65,16 +73,18 @@ export class GuidedOnboardingPlugin theme$, api, application, + notifications, }: { targetDomElement: HTMLElement; theme$: Rx.Observable; api: ApiService; application: ApplicationStart; + notifications: NotificationsStart; }) { ReactDOM.render( - + , targetDomElement diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index 4b5505482592ad..94fe675d9fb06f 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -10,6 +10,8 @@ import { HttpSetup } from '@kbn/core/public'; import { BehaviorSubject, map, Observable, firstValueFrom, concat, of } from 'rxjs'; import type { GuideState, GuideId, GuideStep, GuideStepIds } from '@kbn/guided-onboarding'; +import { API_BASE_PATH } from '../../common/constants'; +import { PluginState, PluginStatus } from '../../common/types'; import { GuidedOnboardingApi } from '../types'; import { getGuideConfig, @@ -22,8 +24,6 @@ import { isStepReadyToComplete, isGuideActive, } from './helpers'; -import { API_BASE_PATH } from '../../common/constants'; -import { PluginState, PluginStatus } from '../../common/types'; export class ApiService implements GuidedOnboardingApi { private isCloudEnabled: boolean | undefined; @@ -89,9 +89,9 @@ export class ApiService implements GuidedOnboardingApi { } /** - * Async operation to fetch state for all guides + * Async operation to fetch state for all guides. * This is useful for the onboarding landing page, - * where all guides are displayed with their corresponding status + * where all guides are displayed with their corresponding status. */ public async fetchAllGuidesState(): Promise<{ state: GuideState[] } | undefined> { if (!this.isCloudEnabled) { @@ -104,18 +104,16 @@ export class ApiService implements GuidedOnboardingApi { try { return await this.client.get<{ state: GuideState[] }>(`${API_BASE_PATH}/guides`); } catch (error) { - // TODO handle error - // eslint-disable-next-line no-console - console.error(error); + throw error; } } /** - * Updates the SO with the updated guide state and refreshes the observables - * This is largely used internally and for tests - * @param {GuideState} newState the updated guide state + * Updates the SO with the updated plugin state and refreshes the observables. + * This is largely used internally and for tests. + * @param {{status?: PluginStatus; guide?: GuideState}} state the updated plugin state * @param {boolean} panelState boolean to determine whether the dropdown panel should open or not - * @return {Promise} a promise with the updated guide state + * @return {Promise} a promise with the updated plugin state or undefined */ public async updatePluginState( state: { status?: PluginStatus; guide?: GuideState }, @@ -140,18 +138,16 @@ export class ApiService implements GuidedOnboardingApi { this.isGuidePanelOpen$.next(panelState); return response; } catch (error) { - // TODO handle error - // eslint-disable-next-line no-console - console.error(error); + throw error; } } /** - * Activates a guide by guideId - * This is useful for the onboarding landing page, when a user selects a guide to start or continue + * Activates a guide by guideId. + * This is useful for the onboarding landing page, when a user selects a guide to start or continue. * @param {GuideId} guideId the id of the guide (one of search, observability, security) * @param {GuideState} guide (optional) the selected guide state, if it exists (i.e., if a user is continuing a guide) - * @return {Promise} a promise with the updated guide state + * @return {Promise} a promise with the updated plugin state */ public async activateGuide( guideId: GuideId, @@ -203,10 +199,10 @@ export class ApiService implements GuidedOnboardingApi { } /** - * Marks a guide as inactive - * This is useful for the dropdown panel, when a user quits a guide + * Marks a guide as inactive. + * This is useful for the dropdown panel, when a user quits a guide. * @param {GuideState} guide the selected guide state - * @return {Promise} a promise with the updated guide state + * @return {Promise} a promise with the updated plugin state */ public async deactivateGuide( guide: GuideState @@ -224,11 +220,11 @@ export class ApiService implements GuidedOnboardingApi { } /** - * Completes a guide - * Updates the overall guide status to 'complete', and marks it as inactive - * This is useful for the dropdown panel, when the user clicks the "Continue using Elastic" button after completing all steps + * Completes a guide. + * Updates the overall guide status to 'complete', and marks it as inactive. + * This is useful for the dropdown panel, when the user clicks the "Continue using Elastic" button after completing all steps. * @param {GuideId} guideId the id of the guide (one of search, observability, security) - * @return {Promise} a promise with the updated guide state + * @return {Promise} a promise with the updated plugin state */ public async completeGuide(guideId: GuideId): Promise<{ pluginState: PluginState } | undefined> { const pluginState = await firstValueFrom(this.fetchPluginState$()); @@ -272,11 +268,11 @@ export class ApiService implements GuidedOnboardingApi { } /** - * Updates the selected step to 'in_progress' state - * This is useful for the dropdown panel, when the user clicks the "Start" button for the active step + * Updates the selected step to 'in_progress' state. + * This is useful for the dropdown panel, when the user clicks the "Start" button for the active step. * @param {GuideId} guideId the id of the guide (one of search, observability, security) * @param {GuideStepIds} stepId the id of the step - * @return {Promise} a promise with the updated guide state + * @return {Promise} a promise with the updated plugin state */ public async startGuideStep( guideId: GuideId, @@ -379,6 +375,12 @@ export class ApiService implements GuidedOnboardingApi { ); } + /** + * Completes the guide step identified by the integration. + * A noop if the active step is not configured with the passed integration. + * @param {GuideId} integration the integration (package name) that identifies the active guide step + * @return {Promise} a promise with the updated state or undefined if the operation fails + */ public async completeGuidedOnboardingForIntegration( integration?: string ): Promise<{ pluginState: PluginState } | undefined> { @@ -394,8 +396,12 @@ export class ApiService implements GuidedOnboardingApi { } } + /** + * Sets the plugin state to "skipped". + * This is used on the landing page when the user clicks the button to skip the guided setup. + * @return {Promise} a promise with the updated state or undefined if the operation fails + */ public async skipGuidedOnboarding(): Promise<{ pluginState: PluginState } | undefined> { - // TODO error handling and loading state return await this.updatePluginState({ status: 'skipped' }, false); } } diff --git a/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap b/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap index 685e36e46810c4..ace1c2e32c21f1 100644 --- a/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap +++ b/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap @@ -57,7 +57,7 @@ exports[`getting started should render getting started component 1`] = ` > @@ -88,7 +88,7 @@ exports[`getting started should render getting started component 1`] = ` > { - 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(), - uiSettings: uiSettingsMock, - http: { - basePath: { - prepend: jest.fn(), - }, - }, - guidedOnboardingService: { - fetchAllGuidesState: jest.fn(), - skipGuidedOnboarding: jest.fn(), - }, - }), - }; -}); +const mockCloud = cloudMock.createSetup(); +const mockChrome = chromeServiceMock.createStartContract(); +const mockApplication = applicationServiceMock.createStartContract(); +const mockSettingsUI = uiSettingsServiceMock.createSetupContract(); +mockSettingsUI.get.mockReturnValue(false); +const mockHttp = httpServiceMock.createStartContract(); +const mockApiService = new ApiService(); +mockApiService.setup(mockHttp, true); + +jest.mock('../../kibana_services', () => ({ + getServices: () => ({ + cloud: mockCloud, + chrome: mockChrome, + application: mockApplication, + trackUiMetric: jest.fn(), + uiSettings: mockSettingsUI, + http: mockHttp, + guidedOnboardingService: mockApiService, + }), +})); + describe('getting started', () => { let storageItemValue: string | null; + let testBed: TestBed; beforeAll(() => { storageItemValue = localStorage.getItem(KEY_ENABLE_WELCOME); localStorage.removeItem(KEY_ENABLE_WELCOME); @@ -52,21 +51,56 @@ describe('getting started', () => { localStorage.setItem(KEY_ENABLE_WELCOME, storageItemValue); } }); + test('should render getting started component', async () => { const component = await shallow(); expect(component).toMatchSnapshot(); }); + test('displays loading indicator', async () => { + mockHttp.get.mockImplementationOnce(() => { + return new Promise((resolve) => + setTimeout(() => { + resolve({ state: [] }); + }, 1000) + ); + }); + mockApiService.setup(mockHttp, true); + + await act(async () => { + testBed = registerTestBed(GettingStarted)(); + }); + testBed!.component.update(); + expect(findTestSubject(testBed!.component, 'onboarding--loadingIndicator').exists()).toBe(true); + }); + + test('displays error section', async () => { + mockHttp.get.mockRejectedValueOnce(new Error('request failed')); + mockApiService.setup(mockHttp, true); + + await act(async () => { + testBed = registerTestBed(GettingStarted)(); + }); + testBed!.component.update(); + expect(findTestSubject(testBed!.component, 'onboarding--errorSection').exists()).toBe(true); + }); + test('skip button should disable home welcome screen', async () => { - const component = mountWithIntl(); - const skipButton = findTestSubject(component, 'onboarding--skipGuideLink'); + mockHttp.get.mockResolvedValueOnce({ state: [] }); + mockApiService.setup(mockHttp, true); await act(async () => { - await skipButton.simulate('click'); + testBed = registerTestBed(GettingStarted)(); }); + testBed!.component.update(); + + const skipButton = findTestSubject(testBed!.component, 'onboarding--skipGuideLink'); - component.update(); + await act(async () => { + await skipButton.simulate('click'); + }); + testBed!.component.update(); expect(localStorage.getItem(KEY_ENABLE_WELCOME)).toBe('false'); }); 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 7a6992da521b64..6e4d605cc30b3e 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 @@ -8,10 +8,12 @@ import React, { useCallback, useEffect, useState } from 'react'; import { + EuiButton, EuiFlexGrid, EuiFlexItem, EuiHorizontalRule, EuiLink, + EuiLoadingSpinner, EuiPageTemplate, EuiPanel, EuiSpacer, @@ -33,7 +35,7 @@ import { KEY_ENABLE_WELCOME } from '../home'; const homeBreadcrumb = i18n.translate('home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); const gettingStartedBreadcrumb = i18n.translate('home.breadcrumbs.gettingStartedTitle', { - defaultMessage: 'Guided setup', + defaultMessage: 'Setup guides', }); const title = i18n.translate('home.guidedOnboarding.gettingStarted.useCaseSelectionTitle', { defaultMessage: 'What would you like to do first?', @@ -49,6 +51,8 @@ export const GettingStarted = () => { const { application, trackUiMetric, chrome, guidedOnboardingService, http, uiSettings, cloud } = getServices(); const [guidesState, setGuidesState] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isError, setIsError] = useState(false); const history = useHistory(); useEffect(() => { @@ -68,9 +72,17 @@ export const GettingStarted = () => { }, [chrome, trackUiMetric]); const fetchGuidesState = useCallback(async () => { - const allGuides = await guidedOnboardingService?.fetchAllGuidesState(); - if (allGuides) { - setGuidesState(allGuides.state); + setIsLoading(true); + setIsError(false); + try { + const allGuides = await guidedOnboardingService?.fetchAllGuidesState(); + setIsLoading(false); + if (allGuides) { + setGuidesState(allGuides.state); + } + } catch (error) { + setIsLoading(false); + setIsError(true); } }, [guidedOnboardingService]); @@ -85,7 +97,12 @@ export const GettingStarted = () => { }, [cloud, history]); const onSkip = async () => { - await guidedOnboardingService?.skipGuidedOnboarding(); + try { + await guidedOnboardingService?.skipGuidedOnboarding(); + } catch (error) { + // if the state update fails, it's safe to ignore the error + } + trackUiMetric(METRIC_TYPE.CLICK, 'guided_onboarding__skipped'); // disable welcome screen on the home page localStorage.setItem(KEY_ENABLE_WELCOME, JSON.stringify(false)); @@ -98,10 +115,71 @@ export const GettingStarted = () => { const isDarkTheme = uiSettings.get('theme:darkMode'); const activateGuide = async (useCase: UseCase, guideState?: GuideState) => { - await guidedOnboardingService?.activateGuide(useCase as GuideId, guideState); - // TODO error handling https://github.com/elastic/kibana/issues/139798 + try { + await guidedOnboardingService?.activateGuide(useCase as GuideId, guideState); + } catch (err) { + getServices().toastNotifications.addDanger({ + title: i18n.translate('home.guidedOnboarding.gettingStarted.activateGuide.errorMessage', { + defaultMessage: 'Unable to start the guide. Please try again later.', + }), + text: err.message, + }); + } }; + if (isLoading) { + return ( + } + body={ + + {i18n.translate('home.guidedOnboarding.gettingStarted.loadingIndicator', { + defaultMessage: 'Loading setup guide state...', + })} + + } + data-test-subj="onboarding--loadingIndicator" + /> + ); + } + + if (isError) { + return ( + + {i18n.translate('home.guidedOnboarding.gettingStarted.errorSectionTitle', { + defaultMessage: 'Unable to load guide state', + })} + + } + body={ + <> + + {i18n.translate('home.guidedOnboarding.gettingStarted.errorSectionDescription', { + defaultMessage: 'There was an error loading the guide state. Try again later.', + })} + + + + {i18n.translate('home.guidedOnboarding.gettingStarted.errorSectionRefreshButton', { + defaultMessage: 'Refresh', + })} + + + } + data-test-subj="onboarding--errorSection" + /> + ); + } + return ( diff --git a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.stories.tsx b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.stories.tsx index 127e60638fd41c..b854cc1a5cfff5 100644 --- a/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.stories.tsx +++ b/src/plugins/presentation_util/public/components/data_view_picker/data_view_picker.stories.tsx @@ -11,9 +11,9 @@ import React, { useState } from 'react'; import useMount from 'react-use/lib/useMount'; import { DataView, DataViewListItem } from '@kbn/data-views-plugin/common'; import { DataViewPicker } from './data_view_picker'; -import { injectStorybookDataView } from '../../services/storybook/data_views'; +import { injectStorybookDataView } from '../../services/data_views/data_views.story'; import { storybookFlightsDataView } from '../../mocks'; -import { pluginServices, registry, StorybookParams } from '../../services/storybook'; +import { pluginServices, registry, StorybookParams } from '../../services/plugin_services.story'; export default { component: DataViewPicker, diff --git a/src/plugins/presentation_util/public/components/labs/project_list_item.stories.tsx b/src/plugins/presentation_util/public/components/labs/project_list_item.stories.tsx index bc6c123c21f34e..42e06d9f3ba492 100644 --- a/src/plugins/presentation_util/public/components/labs/project_list_item.stories.tsx +++ b/src/plugins/presentation_util/public/components/labs/project_list_item.stories.tsx @@ -11,7 +11,7 @@ import { action } from '@storybook/addon-actions'; import { mapValues } from 'lodash'; import { EnvironmentStatus, ProjectConfig, ProjectID, ProjectStatus } from '../../../common'; -import { applyProjectStatus } from '../../services/labs'; +import { applyProjectStatus } from '../../services/labs/types'; import { ProjectListItem, Props } from './project_list_item'; import { projects as projectConfigs } from '../../../common'; diff --git a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.stories.tsx b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.stories.tsx index 341f194b71ba40..6f4edc1bd28532 100644 --- a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.stories.tsx +++ b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.stories.tsx @@ -9,7 +9,7 @@ import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; -import { StorybookParams } from '../services/storybook'; +import { StorybookParams } from '../services/plugin_services.story'; import { SaveModalDashboardSelector } from './saved_object_save_modal_dashboard_selector'; export default { diff --git a/src/plugins/presentation_util/public/index.ts b/src/plugins/presentation_util/public/index.ts index a5004b778f2b1f..dd27530b30c7d4 100644 --- a/src/plugins/presentation_util/public/index.ts +++ b/src/plugins/presentation_util/public/index.ts @@ -18,7 +18,6 @@ export type { PresentationDashboardsService, PresentationLabsService, } from './services'; -export { getStubPluginServices } from './services'; export type { KibanaPluginServiceFactory, diff --git a/src/plugins/presentation_util/public/mocks.ts b/src/plugins/presentation_util/public/mocks.ts index 53cbce3469c678..8804c21bf6d9ce 100644 --- a/src/plugins/presentation_util/public/mocks.ts +++ b/src/plugins/presentation_util/public/mocks.ts @@ -9,7 +9,7 @@ import { CoreStart } from '@kbn/core/public'; import { PresentationUtilPluginStart } from './types'; import { pluginServices } from './services'; -import { registry } from './services/kibana'; +import { registry } from './services/plugin_services'; import { registerExpressionsLanguage } from '.'; const createStartContract = (coreStart: CoreStart): PresentationUtilPluginStart => { diff --git a/src/plugins/presentation_util/public/plugin.ts b/src/plugins/presentation_util/public/plugin.ts index de6898b107a2d4..d01abeb8f6559a 100644 --- a/src/plugins/presentation_util/public/plugin.ts +++ b/src/plugins/presentation_util/public/plugin.ts @@ -7,8 +7,7 @@ */ import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; -import { pluginServices } from './services'; -import { registry } from './services/kibana'; +import { pluginServices, registry } from './services/plugin_services'; import { PresentationUtilPluginSetupDeps, PresentationUtilPluginStartDeps, diff --git a/src/plugins/presentation_util/public/services/storybook/capabilities.ts b/src/plugins/presentation_util/public/services/capabilities/capabilities.story.ts similarity index 89% rename from src/plugins/presentation_util/public/services/storybook/capabilities.ts rename to src/plugins/presentation_util/public/services/capabilities/capabilities.story.ts index 1dd8cfd571e5c5..a94483f40d1720 100644 --- a/src/plugins/presentation_util/public/services/storybook/capabilities.ts +++ b/src/plugins/presentation_util/public/services/capabilities/capabilities.story.ts @@ -7,8 +7,8 @@ */ import { PluginServiceFactory } from '../create'; -import { StorybookParams } from '.'; -import { PresentationCapabilitiesService } from '../capabilities'; +import { StorybookParams } from '../plugin_services.story'; +import { PresentationCapabilitiesService } from './types'; type CapabilitiesServiceFactory = PluginServiceFactory< PresentationCapabilitiesService, diff --git a/src/plugins/presentation_util/public/services/stub/capabilities.ts b/src/plugins/presentation_util/public/services/capabilities/capabilities.stub.ts similarity index 91% rename from src/plugins/presentation_util/public/services/stub/capabilities.ts rename to src/plugins/presentation_util/public/services/capabilities/capabilities.stub.ts index be1be966285f74..8e6ed26d3139be 100644 --- a/src/plugins/presentation_util/public/services/stub/capabilities.ts +++ b/src/plugins/presentation_util/public/services/capabilities/capabilities.stub.ts @@ -7,7 +7,7 @@ */ import { PluginServiceFactory } from '../create'; -import { PresentationCapabilitiesService } from '../capabilities'; +import { PresentationCapabilitiesService } from './types'; type CapabilitiesServiceFactory = PluginServiceFactory; diff --git a/src/plugins/presentation_util/public/services/kibana/capabilities.ts b/src/plugins/presentation_util/public/services/capabilities/capabilities_service.ts similarity index 94% rename from src/plugins/presentation_util/public/services/kibana/capabilities.ts rename to src/plugins/presentation_util/public/services/capabilities/capabilities_service.ts index 7b12a9a3cc6185..1693464e664705 100644 --- a/src/plugins/presentation_util/public/services/kibana/capabilities.ts +++ b/src/plugins/presentation_util/public/services/capabilities/capabilities_service.ts @@ -8,7 +8,7 @@ import { PresentationUtilPluginStartDeps } from '../../types'; import { KibanaPluginServiceFactory } from '../create'; -import { PresentationCapabilitiesService } from '../capabilities'; +import { PresentationCapabilitiesService } from './types'; export type CapabilitiesServiceFactory = KibanaPluginServiceFactory< PresentationCapabilitiesService, diff --git a/src/plugins/presentation_util/public/services/capabilities.ts b/src/plugins/presentation_util/public/services/capabilities/types.ts similarity index 100% rename from src/plugins/presentation_util/public/services/capabilities.ts rename to src/plugins/presentation_util/public/services/capabilities/types.ts diff --git a/src/plugins/presentation_util/public/services/stub/dashboards.ts b/src/plugins/presentation_util/public/services/dashboards/dashboards.stub.ts similarity index 94% rename from src/plugins/presentation_util/public/services/stub/dashboards.ts rename to src/plugins/presentation_util/public/services/dashboards/dashboards.stub.ts index 047176836896bb..d1dabd027aa2f6 100644 --- a/src/plugins/presentation_util/public/services/stub/dashboards.ts +++ b/src/plugins/presentation_util/public/services/dashboards/dashboards.stub.ts @@ -7,7 +7,7 @@ */ import { PluginServiceFactory } from '../create'; -import { PresentationDashboardsService } from '../dashboards'; +import { PresentationDashboardsService } from './types'; // TODO (clint): Create set of dashboards to stub and return. diff --git a/src/plugins/presentation_util/public/services/kibana/dashboards.ts b/src/plugins/presentation_util/public/services/dashboards/dashboards_service.ts similarity index 95% rename from src/plugins/presentation_util/public/services/kibana/dashboards.ts rename to src/plugins/presentation_util/public/services/dashboards/dashboards_service.ts index 59e3ada10a869c..68facb8f31d94b 100644 --- a/src/plugins/presentation_util/public/services/kibana/dashboards.ts +++ b/src/plugins/presentation_util/public/services/dashboards/dashboards_service.ts @@ -8,7 +8,7 @@ import { PresentationUtilPluginStartDeps } from '../../types'; import { KibanaPluginServiceFactory } from '../create'; -import { PresentationDashboardsService } from '../dashboards'; +import type { PresentationDashboardsService } from './types'; export type DashboardsServiceFactory = KibanaPluginServiceFactory< PresentationDashboardsService, @@ -18,7 +18,6 @@ export type DashboardsServiceFactory = KibanaPluginServiceFactory< export interface PartialDashboardAttributes { title: string; } - export const dashboardsServiceFactory: DashboardsServiceFactory = ({ coreStart }) => { const findDashboards = async (query: string = '', fields: string[] = []) => { const { find } = coreStart.savedObjects.client; diff --git a/src/plugins/presentation_util/public/services/dashboards.ts b/src/plugins/presentation_util/public/services/dashboards/types.ts similarity index 91% rename from src/plugins/presentation_util/public/services/dashboards.ts rename to src/plugins/presentation_util/public/services/dashboards/types.ts index 1ee60f6a3b26b6..a4fd17a2818004 100644 --- a/src/plugins/presentation_util/public/services/dashboards.ts +++ b/src/plugins/presentation_util/public/services/dashboards/types.ts @@ -7,7 +7,7 @@ */ import { SimpleSavedObject } from '@kbn/core/public'; -import { PartialDashboardAttributes } from './kibana/dashboards'; +import { PartialDashboardAttributes } from './dashboards_service'; export interface PresentationDashboardsService { findDashboards: ( diff --git a/src/plugins/presentation_util/public/services/storybook/data_views.ts b/src/plugins/presentation_util/public/services/data_views/data_views.story.ts similarity index 95% rename from src/plugins/presentation_util/public/services/storybook/data_views.ts rename to src/plugins/presentation_util/public/services/data_views/data_views.story.ts index 4ae31925deca09..fc20fe2a7db7aa 100644 --- a/src/plugins/presentation_util/public/services/storybook/data_views.ts +++ b/src/plugins/presentation_util/public/services/data_views/data_views.story.ts @@ -9,7 +9,7 @@ import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { DataView } from '@kbn/data-views-plugin/common'; import { PluginServiceFactory } from '../create'; -import { PresentationDataViewsService } from '../data_views'; +import { PresentationDataViewsService } from './types'; export type DataViewsServiceFactory = PluginServiceFactory; diff --git a/src/plugins/presentation_util/public/services/kibana/data_views.ts b/src/plugins/presentation_util/public/services/data_views/data_views_service.ts similarity index 93% rename from src/plugins/presentation_util/public/services/kibana/data_views.ts rename to src/plugins/presentation_util/public/services/data_views/data_views_service.ts index ecebecce3b3c02..8351a2f9ee2a19 100644 --- a/src/plugins/presentation_util/public/services/kibana/data_views.ts +++ b/src/plugins/presentation_util/public/services/data_views/data_views_service.ts @@ -7,7 +7,7 @@ */ import { PresentationUtilPluginStartDeps } from '../../types'; -import { PresentationDataViewsService } from '../data_views'; +import { PresentationDataViewsService } from './types'; import { KibanaPluginServiceFactory } from '../create'; export type DataViewsServiceFactory = KibanaPluginServiceFactory< diff --git a/src/plugins/presentation_util/public/services/data_views.ts b/src/plugins/presentation_util/public/services/data_views/types.ts similarity index 100% rename from src/plugins/presentation_util/public/services/data_views.ts rename to src/plugins/presentation_util/public/services/data_views/types.ts diff --git a/src/plugins/presentation_util/public/services/index.ts b/src/plugins/presentation_util/public/services/index.ts index a264c8f7049ed1..b95bb666ae4e21 100644 --- a/src/plugins/presentation_util/public/services/index.ts +++ b/src/plugins/presentation_util/public/services/index.ts @@ -6,34 +6,10 @@ * Side Public License, v 1. */ -import { PresentationUtilPluginStart } from '../types'; -import { PluginServices } from './create'; -import { PresentationCapabilitiesService } from './capabilities'; -import { PresentationDashboardsService } from './dashboards'; -import { PresentationLabsService } from './labs'; -// eslint-disable-next-line @kbn/imports/no_boundary_crossing -import { registry as stubRegistry } from './stub'; -import { PresentationDataViewsService } from './data_views'; -import { registerExpressionsLanguage } from '..'; +export { pluginServices } from './plugin_services'; -export type { PresentationCapabilitiesService } from './capabilities'; -export type { PresentationDashboardsService } from './dashboards'; -export type { PresentationLabsService } from './labs'; - -export interface PresentationUtilServices { - dashboards: PresentationDashboardsService; - dataViews: PresentationDataViewsService; - capabilities: PresentationCapabilitiesService; - labs: PresentationLabsService; -} - -export const pluginServices = new PluginServices(); - -export const getStubPluginServices = (): PresentationUtilPluginStart => { - pluginServices.setRegistry(stubRegistry.start({})); - return { - ContextProvider: pluginServices.getContextProvider(), - labsService: pluginServices.getServices().labs, - registerExpressionsLanguage, - }; -}; +export type { + PresentationCapabilitiesService, + PresentationDashboardsService, + PresentationLabsService, +} from './types'; diff --git a/src/plugins/presentation_util/public/services/storybook/labs.ts b/src/plugins/presentation_util/public/services/labs/labs.story.ts similarity index 98% rename from src/plugins/presentation_util/public/services/storybook/labs.ts rename to src/plugins/presentation_util/public/services/labs/labs.story.ts index 8bc526987d95fd..171b31188f3229 100644 --- a/src/plugins/presentation_util/public/services/storybook/labs.ts +++ b/src/plugins/presentation_util/public/services/labs/labs.story.ts @@ -9,7 +9,7 @@ import { EnvironmentName, projectIDs, Project } from '../../../common'; import { PluginServiceFactory } from '../create'; import { projects, ProjectID, getProjectIDs, SolutionName } from '../../../common'; -import { PresentationLabsService, isEnabledByStorageValue, applyProjectStatus } from '../labs'; +import { PresentationLabsService, isEnabledByStorageValue, applyProjectStatus } from './types'; export type LabsServiceFactory = PluginServiceFactory; diff --git a/src/plugins/presentation_util/public/services/stub/labs.ts b/src/plugins/presentation_util/public/services/labs/labs.stub.ts similarity index 98% rename from src/plugins/presentation_util/public/services/stub/labs.ts rename to src/plugins/presentation_util/public/services/labs/labs.stub.ts index 5192f5f090fec7..40706c8a261b58 100644 --- a/src/plugins/presentation_util/public/services/stub/labs.ts +++ b/src/plugins/presentation_util/public/services/labs/labs.stub.ts @@ -16,7 +16,7 @@ import { SolutionName, } from '../../../common'; import { PluginServiceFactory } from '../create'; -import { PresentationLabsService, isEnabledByStorageValue, applyProjectStatus } from '../labs'; +import { PresentationLabsService, isEnabledByStorageValue, applyProjectStatus } from './types'; export type LabsServiceFactory = PluginServiceFactory; diff --git a/src/plugins/presentation_util/public/services/kibana/labs.ts b/src/plugins/presentation_util/public/services/labs/labs_service.ts similarity index 99% rename from src/plugins/presentation_util/public/services/kibana/labs.ts rename to src/plugins/presentation_util/public/services/labs/labs_service.ts index fe0767ff09d8f4..9f163d83cd01ca 100644 --- a/src/plugins/presentation_util/public/services/kibana/labs.ts +++ b/src/plugins/presentation_util/public/services/labs/labs_service.ts @@ -24,7 +24,7 @@ import { setStorageStatus, setUISettingsStatus, applyProjectStatus, -} from '../labs'; +} from './types'; export type LabsServiceFactory = KibanaPluginServiceFactory< PresentationLabsService, diff --git a/src/plugins/presentation_util/public/services/labs.ts b/src/plugins/presentation_util/public/services/labs/types.ts similarity index 98% rename from src/plugins/presentation_util/public/services/labs.ts rename to src/plugins/presentation_util/public/services/labs/types.ts index 0a1fbe31c34b8b..443a171bfca774 100644 --- a/src/plugins/presentation_util/public/services/labs.ts +++ b/src/plugins/presentation_util/public/services/labs/types.ts @@ -17,7 +17,7 @@ import { environmentNames, isProjectEnabledByStatus, SolutionName, -} from '../../common'; +} from '../../../common'; export interface PresentationLabsService { isProjectEnabled: (id: ProjectID) => boolean; diff --git a/src/plugins/presentation_util/public/services/storybook/index.ts b/src/plugins/presentation_util/public/services/plugin_services.story.ts similarity index 69% rename from src/plugins/presentation_util/public/services/storybook/index.ts rename to src/plugins/presentation_util/public/services/plugin_services.story.ts index 18333bd5522ca8..8eb38a3dee0198 100644 --- a/src/plugins/presentation_util/public/services/storybook/index.ts +++ b/src/plugins/presentation_util/public/services/plugin_services.story.ts @@ -11,31 +11,28 @@ import { PluginServiceProviders, PluginServiceProvider, PluginServiceRegistry, -} from '../create'; -import { dashboardsServiceFactory } from '../stub/dashboards'; -import { labsServiceFactory } from './labs'; -import { capabilitiesServiceFactory } from './capabilities'; -import { PresentationUtilServices } from '..'; -import { dataViewsServiceFactory } from './data_views'; +} from './create'; +import { PresentationUtilServices } from './types'; -export type { PluginServiceProviders } from '../create'; -export { PluginServiceProvider, PluginServiceRegistry } from '../create'; -export type { PresentationUtilServices } from '..'; +import { capabilitiesServiceFactory } from './capabilities/capabilities.story'; +import { dataViewsServiceFactory } from './data_views/data_views.story'; +import { dashboardsServiceFactory } from './dashboards/dashboards.stub'; +import { labsServiceFactory } from './labs/labs.story'; -export interface StorybookParams { - canAccessDashboards?: boolean; - canCreateNewDashboards?: boolean; - canSaveVisualizations?: boolean; - canSetAdvancedSettings?: boolean; -} - -export const providers: PluginServiceProviders = { +export const providers: PluginServiceProviders = { capabilities: new PluginServiceProvider(capabilitiesServiceFactory), - dashboards: new PluginServiceProvider(dashboardsServiceFactory), - dataViews: new PluginServiceProvider(dataViewsServiceFactory), labs: new PluginServiceProvider(labsServiceFactory), + dataViews: new PluginServiceProvider(dataViewsServiceFactory), + dashboards: new PluginServiceProvider(dashboardsServiceFactory), }; export const pluginServices = new PluginServices(); export const registry = new PluginServiceRegistry(providers); + +export interface StorybookParams { + canAccessDashboards?: boolean; + canCreateNewDashboards?: boolean; + canSaveVisualizations?: boolean; + canSetAdvancedSettings?: boolean; +} diff --git a/src/plugins/presentation_util/public/services/plugin_services.stub.ts b/src/plugins/presentation_util/public/services/plugin_services.stub.ts new file mode 100644 index 00000000000000..8f69efbcbe0c40 --- /dev/null +++ b/src/plugins/presentation_util/public/services/plugin_services.stub.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 { PluginServices, PluginServiceProviders, PluginServiceProvider } from './create'; +import { PresentationUtilServices } from './types'; +import { registry as stubRegistry } from './plugin_services.story'; +import { PresentationUtilPluginStart, registerExpressionsLanguage } from '..'; + +import { capabilitiesServiceFactory } from './capabilities/capabilities.story'; +import { dataViewsServiceFactory } from './data_views/data_views.story'; +import { dashboardsServiceFactory } from './dashboards/dashboards.stub'; +import { labsServiceFactory } from './labs/labs.story'; + +export const providers: PluginServiceProviders = { + capabilities: new PluginServiceProvider(capabilitiesServiceFactory), + labs: new PluginServiceProvider(labsServiceFactory), + dataViews: new PluginServiceProvider(dataViewsServiceFactory), + dashboards: new PluginServiceProvider(dashboardsServiceFactory), +}; + +export const pluginServices = new PluginServices(); + +export const getStubPluginServices = (): PresentationUtilPluginStart => { + pluginServices.setRegistry(stubRegistry.start({})); + return { + ContextProvider: pluginServices.getContextProvider(), + labsService: pluginServices.getServices().labs, + registerExpressionsLanguage, + }; +}; diff --git a/src/plugins/presentation_util/public/services/kibana/index.ts b/src/plugins/presentation_util/public/services/plugin_services.ts similarity index 66% rename from src/plugins/presentation_util/public/services/kibana/index.ts rename to src/plugins/presentation_util/public/services/plugin_services.ts index e412ca5ab1b484..04f36836bd1ffc 100644 --- a/src/plugins/presentation_util/public/services/kibana/index.ts +++ b/src/plugins/presentation_util/public/services/plugin_services.ts @@ -7,18 +7,19 @@ */ import { + PluginServices, PluginServiceProviders, KibanaPluginServiceParams, PluginServiceProvider, PluginServiceRegistry, -} from '../create'; -import { PresentationUtilPluginStartDeps } from '../../types'; -import { PresentationUtilServices } from '..'; +} from './create'; +import { PresentationUtilPluginStartDeps } from '../types'; -import { capabilitiesServiceFactory } from './capabilities'; -import { dataViewsServiceFactory } from './data_views'; -import { dashboardsServiceFactory } from './dashboards'; -import { labsServiceFactory } from './labs'; +import { capabilitiesServiceFactory } from './capabilities/capabilities_service'; +import { dataViewsServiceFactory } from './data_views/data_views_service'; +import { dashboardsServiceFactory } from './dashboards/dashboards_service'; +import { labsServiceFactory } from './labs/labs_service'; +import { PresentationUtilServices } from './types'; export const providers: PluginServiceProviders< PresentationUtilServices, @@ -30,6 +31,8 @@ export const providers: PluginServiceProviders< dashboards: new PluginServiceProvider(dashboardsServiceFactory), }; +export const pluginServices = new PluginServices(); + export const registry = new PluginServiceRegistry< PresentationUtilServices, KibanaPluginServiceParams diff --git a/src/plugins/presentation_util/public/services/stub/index.ts b/src/plugins/presentation_util/public/services/stub/index.ts deleted file mode 100644 index 34a13d030e5356..00000000000000 --- a/src/plugins/presentation_util/public/services/stub/index.ts +++ /dev/null @@ -1,26 +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 { capabilitiesServiceFactory } from './capabilities'; -import { dashboardsServiceFactory } from './dashboards'; -import { labsServiceFactory } from './labs'; -import { PluginServiceProviders, PluginServiceProvider, PluginServiceRegistry } from '../create'; -import { PresentationUtilServices } from '..'; -export { dashboardsServiceFactory } from './dashboards'; -export { capabilitiesServiceFactory } from './capabilities'; - -import { dataViewsServiceFactory } from '../storybook/data_views'; - -export const providers: PluginServiceProviders = { - dashboards: new PluginServiceProvider(dashboardsServiceFactory), - capabilities: new PluginServiceProvider(capabilitiesServiceFactory), - labs: new PluginServiceProvider(labsServiceFactory), - dataViews: new PluginServiceProvider(dataViewsServiceFactory), -}; - -export const registry = new PluginServiceRegistry(providers); diff --git a/src/plugins/presentation_util/public/services/types.ts b/src/plugins/presentation_util/public/services/types.ts new file mode 100644 index 00000000000000..b2d32788b1762a --- /dev/null +++ b/src/plugins/presentation_util/public/services/types.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 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 { PresentationLabsService } from './labs/types'; +import { PresentationDashboardsService } from './dashboards/types'; +import { PresentationCapabilitiesService } from './capabilities/types'; +import { PresentationDataViewsService } from './data_views/types'; + +export interface PresentationUtilServices { + dashboards: PresentationDashboardsService; + dataViews: PresentationDataViewsService; + capabilities: PresentationCapabilitiesService; + labs: PresentationLabsService; +} + +export type { + PresentationCapabilitiesService, + PresentationDashboardsService, + PresentationLabsService, +}; diff --git a/src/plugins/presentation_util/public/types.ts b/src/plugins/presentation_util/public/types.ts index 2cda0aa4bf557e..277c0960b269ef 100644 --- a/src/plugins/presentation_util/public/types.ts +++ b/src/plugins/presentation_util/public/types.ts @@ -8,7 +8,7 @@ import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { registerExpressionsLanguage } from '.'; -import { PresentationLabsService } from './services/labs'; +import { PresentationLabsService } from './services/labs/types'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PresentationUtilPluginSetup {} diff --git a/src/plugins/presentation_util/storybook/decorator.tsx b/src/plugins/presentation_util/storybook/decorator.tsx index 8a6c1126353025..af7d8eea40bf2b 100644 --- a/src/plugins/presentation_util/storybook/decorator.tsx +++ b/src/plugins/presentation_util/storybook/decorator.tsx @@ -12,8 +12,8 @@ import { DecoratorFn } from '@storybook/react'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider as KibanaReactProvider } from '@kbn/kibana-react-plugin/public'; import { pluginServices } from '../public/services'; -import { PresentationUtilServices } from '../public/services'; -import { providers, StorybookParams } from '../public/services/storybook'; +import { PresentationUtilServices } from '../public/services/types'; +import { providers, StorybookParams } from '../public/services/plugin_services.story'; import { PluginServiceRegistry } from '../public/services/create'; const settings = new Map(); diff --git a/src/plugins/unified_field_list/public/components/field_stats/field_stats.test.tsx b/src/plugins/unified_field_list/public/components/field_stats/field_stats.test.tsx index 9e95af12f00a7d..312abc2bb323f9 100644 --- a/src/plugins/unified_field_list/public/components/field_stats/field_stats.test.tsx +++ b/src/plugins/unified_field_list/public/components/field_stats/field_stats.test.tsx @@ -18,8 +18,7 @@ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { DataViewField } from '@kbn/data-views-plugin/common'; import { loadFieldStats } from '../../services/field_stats'; -import FieldStats from './field_stats'; -import type { FieldStatsProps } from './field_stats'; +import FieldStats, { FieldStatsWithKbnQuery } from './field_stats'; jest.mock('../../services/field_stats', () => ({ loadFieldStats: jest.fn().mockResolvedValue({}), @@ -34,7 +33,7 @@ const mockedServices = { }; describe('UnifiedFieldList ', () => { - let defaultProps: FieldStatsProps; + let defaultProps: FieldStatsWithKbnQuery; let dataView: DataView; beforeEach(() => { @@ -202,6 +201,64 @@ describe('UnifiedFieldList ', () => { expect(loadFieldStats).toHaveBeenCalledTimes(1); }); + it('should request field stats with dsl query', async () => { + let resolveFunction: (arg: unknown) => void; + + (loadFieldStats as jest.Mock).mockImplementation(() => { + return new Promise((resolve) => { + resolveFunction = resolve; + }); + }); + + const wrapper = mountWithIntl( + f.name === 'bytes')!, + 'data-test-subj': 'testing', + }} + dslQuery={{ bool: { filter: { range: { field: 'duration', gte: 3000 } } } }} + fromDate="now-14d" + toDate="now-7d" + /> + ); + + await wrapper.update(); + + expect(loadFieldStats).toHaveBeenCalledWith({ + abortController: new AbortController(), + services: { data: mockedServices.data }, + dataView, + dslQuery: { bool: { filter: { range: { field: 'duration', gte: 3000 } } } }, + fromDate: 'now-14d', + toDate: 'now-7d', + field: defaultProps.field, + }); + + expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(1); + + await act(async () => { + resolveFunction!({ + totalDocuments: 4633, + sampledDocuments: 4633, + sampledValues: 4633, + histogram: { + buckets: [{ count: 705, key: 0 }], + }, + topValues: { + buckets: [{ count: 147, key: 0 }], + }, + }); + }); + + await wrapper.update(); + + expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); + + expect(loadFieldStats).toHaveBeenCalledTimes(1); + }); + it('should not request field stats for range fields', async () => { const wrapper = await mountWithIntl( f.name === 'ip_range')!} /> @@ -632,4 +689,74 @@ describe('UnifiedFieldList ', () => { 'Toggle either theTop valuesDistribution1273.9%1326.1%Calculated from 23 sample records.' ); }); + + it('should override the top value bar props with overrideFieldTopValueBar', async () => { + let resolveFunction: (arg: unknown) => void; + + (loadFieldStats as jest.Mock).mockImplementation(() => { + return new Promise((resolve) => { + resolveFunction = resolve; + }); + }); + + const field = dataView.fields.find((f) => f.name === 'machine.ram')!; + + const wrapper = mountWithIntl( + ({ color: 'accent' })} + /> + ); + + await wrapper.update(); + + expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(1); + + await act(async () => { + resolveFunction!({ + totalDocuments: 100, + sampledDocuments: 23, + sampledValues: 23, + histogram: { + buckets: [ + { + count: 17, + key: 12, + }, + { + count: 6, + key: 13, + }, + ], + }, + topValues: { + buckets: [ + { + count: 17, + key: 12, + }, + { + count: 6, + key: 13, + }, + ], + }, + }); + }); + + await wrapper.update(); + + expect(wrapper.find(EuiLoadingSpinner)).toHaveLength(0); + + expect(loadFieldStats).toHaveBeenCalledTimes(1); + + expect(wrapper.find(EuiProgress)).toHaveLength(2); + expect(wrapper.find(EuiProgress).first().props()).toHaveProperty('color', 'accent'); + }); }); 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 b3600dc9f3971e..f6f8063df83343 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 @@ -34,6 +34,7 @@ import { } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; import { buildEsQuery, Query, Filter, AggregateQuery } from '@kbn/es-query'; +import { OverrideFieldTopValueBarCallback } from './field_top_values_bucket'; import type { BucketedAggregation } from '../../../common/types'; import { canProvideStatsForField } from '../../../common/utils/field_stats_utils'; import { loadFieldStats } from '../../services/field_stats'; @@ -46,7 +47,7 @@ import { } from './field_top_values'; import { FieldSummaryMessage } from './field_summary_message'; -interface State { +export interface FieldStatsState { isLoading: boolean; totalDocuments?: number; sampledDocuments?: number; @@ -63,11 +64,11 @@ export interface FieldStatsServices { charts: ChartsPluginSetup; } -export interface FieldStatsProps { +interface FieldStatsPropsBase { services: FieldStatsServices; - query: Query | AggregateQuery; - filters: Filter[]; + /** ISO formatted date string **/ fromDate: string; + /** ISO formatted date string **/ toDate: string; dataViewOrDataViewId: DataView | string; field: DataViewField; @@ -83,11 +84,30 @@ export interface FieldStatsProps { sampledDocuments?: number; }) => JSX.Element; onAddFilter?: AddFieldFilterHandler; + overrideFieldTopValueBar?: OverrideFieldTopValueBarCallback; + onStateChange?: (s: FieldStatsState) => void; +} + +export interface FieldStatsWithKbnQuery extends FieldStatsPropsBase { + /** If Kibana-supported query is provided, it will be converted to dsl query **/ + query: Query | AggregateQuery; + filters: Filter[]; + dslQuery?: never; +} + +export interface FieldStatsWithDslQuery extends FieldStatsPropsBase { + query?: never; + filters?: never; + /** If dsl query is provided, use it directly in searches **/ + dslQuery: object; } +export type FieldStatsProps = FieldStatsWithKbnQuery | FieldStatsWithDslQuery; + const FieldStatsComponent: React.FC = ({ services, query, + dslQuery, filters, fromDate, toDate, @@ -98,9 +118,11 @@ const FieldStatsComponent: React.FC = ({ overrideMissingContent, overrideFooter, onAddFilter, + overrideFieldTopValueBar, + onStateChange, }) => { const { fieldFormats, uiSettings, charts, dataViews, data } = services; - const [state, changeState] = useState({ + const [state, changeState] = useState({ isLoading: false, }); const [dataView, changeDataView] = useState(null); @@ -116,6 +138,15 @@ const FieldStatsComponent: React.FC = ({ [changeState, isCanceledRef] ); + useEffect( + function broadcastOnStateChange() { + if (onStateChange) { + onStateChange(state); + } + }, + [onStateChange, state] + ); + const setDataView: typeof changeDataView = useCallback( (nextDataView) => { if (!isCanceledRef.current) { @@ -153,7 +184,9 @@ const FieldStatsComponent: React.FC = ({ field, fromDate, toDate, - dslQuery: buildEsQuery(loadedDataView, query, filters, getEsQueryConfig(uiSettings)), + dslQuery: + dslQuery ?? + buildEsQuery(loadedDataView, query ?? [], filters, getEsQueryConfig(uiSettings)), abortController: abortControllerRef.current, }); @@ -169,7 +202,6 @@ const FieldStatsComponent: React.FC = ({ topValues: results.topValues, })); } catch (e) { - // console.error(e); setState((s) => ({ ...s, isLoading: false })); } } @@ -494,6 +526,7 @@ const FieldStatsComponent: React.FC = ({ color={color} data-test-subj={dataTestSubject} onAddFilter={onAddFilter} + overrideFieldTopValueBar={overrideFieldTopValueBar} /> ); } @@ -511,10 +544,6 @@ class ErrorBoundary extends React.Component<{}, { hasError: boolean }> { return { hasError: true }; } - // componentDidCatch(error, errorInfo) { - // console.log(error, errorInfo); - // } - render() { if (this.state.hasError) { return null; diff --git a/src/plugins/unified_field_list/public/components/field_stats/field_top_values.tsx b/src/plugins/unified_field_list/public/components/field_stats/field_top_values.tsx index adec20e38b7279..1820b610fc5810 100755 --- a/src/plugins/unified_field_list/public/components/field_stats/field_top_values.tsx +++ b/src/plugins/unified_field_list/public/components/field_stats/field_top_values.tsx @@ -11,7 +11,8 @@ import { euiPaletteColorBlind, EuiSpacer } from '@elastic/eui'; import { DataView, DataViewField } from '@kbn/data-plugin/common'; import type { BucketedAggregation } from '../../../common/types'; import type { AddFieldFilterHandler } from '../../types'; -import { FieldTopValuesBucket } from './field_top_values_bucket'; +import FieldTopValuesBucket from './field_top_values_bucket'; +import type { OverrideFieldTopValueBarCallback } from './field_top_values_bucket'; export interface FieldTopValuesProps { buckets: BucketedAggregation['buckets']; @@ -21,6 +22,7 @@ export interface FieldTopValuesProps { color?: string; 'data-test-subj': string; onAddFilter?: AddFieldFilterHandler; + overrideFieldTopValueBar?: OverrideFieldTopValueBarCallback; } export const FieldTopValues: React.FC = ({ @@ -31,6 +33,7 @@ export const FieldTopValues: React.FC = ({ color = getDefaultColor(), 'data-test-subj': dataTestSubject, onAddFilter, + overrideFieldTopValueBar, }) => { if (!buckets?.length) { return null; @@ -65,6 +68,7 @@ export const FieldTopValues: React.FC = ({ color={color} data-test-subj={dataTestSubject} onAddFilter={onAddFilter} + overrideFieldTopValueBar={overrideFieldTopValueBar} /> ); @@ -86,6 +90,7 @@ export const FieldTopValues: React.FC = ({ color={color} data-test-subj={dataTestSubject} onAddFilter={onAddFilter} + overrideFieldTopValueBar={overrideFieldTopValueBar} /> )} diff --git a/src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx b/src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx index e45a55d7b350e5..ccae2a3dfffc14 100755 --- a/src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx +++ b/src/plugins/unified_field_list/public/components/field_stats/field_top_values_bucket.tsx @@ -21,8 +21,7 @@ import type { IFieldSubTypeMulti } from '@kbn/es-query'; import type { DataViewField } from '@kbn/data-views-plugin/common'; import type { AddFieldFilterHandler } from '../../types'; -export interface FieldTopValuesBucketProps { - type?: 'normal' | 'other'; +export interface FieldTopValuesBucketParams { field: DataViewField; fieldValue: unknown; formattedFieldValue?: string; @@ -30,22 +29,43 @@ export interface FieldTopValuesBucketProps { progressValue: number; count: number; color: string; + type?: 'normal' | 'other'; +} + +export type OverrideFieldTopValueBarCallback = ( + params: FieldTopValuesBucketParams +) => Partial; + +export interface FieldTopValuesBucketProps extends FieldTopValuesBucketParams { 'data-test-subj': string; onAddFilter?: AddFieldFilterHandler; + /** + * Optional callback to allow overriding props on bucket level + */ + overrideFieldTopValueBar?: OverrideFieldTopValueBarCallback; } -export const FieldTopValuesBucket: React.FC = ({ - type = 'normal', - field, - fieldValue, - formattedFieldValue, - formattedPercentage, - progressValue, - count, - color, +const FieldTopValuesBucket: React.FC = ({ 'data-test-subj': dataTestSubject, onAddFilter, + overrideFieldTopValueBar, + ...fieldTopValuesBucketOverridableProps }) => { + const overrides = overrideFieldTopValueBar + ? overrideFieldTopValueBar(fieldTopValuesBucketOverridableProps) + : ({} as FieldTopValuesBucketParams); + + const { + field, + type, + fieldValue, + formattedFieldValue, + formattedPercentage, + progressValue, + count, + color, + textProps = {}, + } = { ...fieldTopValuesBucketOverridableProps, ...overrides }; const fieldLabel = (field?.subType as IFieldSubTypeMulti)?.multi?.parent ?? field.name; return ( @@ -69,7 +89,7 @@ export const FieldTopValuesBucket: React.FC = ({ > {(formattedFieldValue?.length ?? 0) > 0 ? ( - + {formattedFieldValue} @@ -175,3 +195,7 @@ export const FieldTopValuesBucket: React.FC = ({ ); }; + +// Necessary for React.lazy +// eslint-disable-next-line import/no-default-export +export default FieldTopValuesBucket; diff --git a/src/plugins/unified_field_list/public/components/field_stats/index.tsx b/src/plugins/unified_field_list/public/components/field_stats/index.tsx index 0cfb29d4abd5fa..1eb4df243e16f5 100755 --- a/src/plugins/unified_field_list/public/components/field_stats/index.tsx +++ b/src/plugins/unified_field_list/public/components/field_stats/index.tsx @@ -7,11 +7,23 @@ */ import React, { Fragment } from 'react'; -import type { FieldStatsProps, FieldStatsServices } from './field_stats'; +import type { FieldStatsProps, FieldStatsServices, FieldStatsState } from './field_stats'; +import type { + FieldTopValuesBucketProps, + FieldTopValuesBucketParams, +} from './field_top_values_bucket'; const Fallback = () => ; +const LazyFieldTopValuesBucket = React.lazy(() => import('./field_top_values_bucket')); const LazyFieldStats = React.lazy(() => import('./field_stats')); + +const WrappedFieldTopValuesBucket: React.FC = (props) => ( + }> + + +); + const WrappedFieldStats: React.FC = (props) => ( }> @@ -19,4 +31,11 @@ const WrappedFieldStats: React.FC = (props) => ( ); export const FieldStats = WrappedFieldStats; -export type { FieldStatsProps, FieldStatsServices }; +export const FieldTopValuesBucket = WrappedFieldTopValuesBucket; +export type { + FieldStatsProps, + FieldStatsServices, + FieldStatsState, + FieldTopValuesBucketProps, + FieldTopValuesBucketParams, +}; diff --git a/src/plugins/unified_field_list/public/index.ts b/src/plugins/unified_field_list/public/index.ts index 94abf515664637..d90a5092576e6c 100755 --- a/src/plugins/unified_field_list/public/index.ts +++ b/src/plugins/unified_field_list/public/index.ts @@ -7,7 +7,6 @@ */ import { UnifiedFieldListPlugin } from './plugin'; - export type { FieldStatsResponse, BucketedAggregation, @@ -15,7 +14,16 @@ export type { TopValuesResult, } from '../common/types'; export { FieldListGrouped, type FieldListGroupedProps } from './components/field_list'; -export type { FieldStatsProps, FieldStatsServices } from './components/field_stats'; +export type { + FieldTopValuesBucketProps, + FieldTopValuesBucketParams, +} from './components/field_stats'; +export { FieldTopValuesBucket } from './components/field_stats'; +export type { + FieldStatsProps, + FieldStatsServices, + FieldStatsState, +} from './components/field_stats'; export { FieldStats } from './components/field_stats'; export { FieldPopover, diff --git a/src/plugins/unified_field_list/public/types.ts b/src/plugins/unified_field_list/public/types.ts index de96cf6a44cfbe..d2c80286f8dea1 100755 --- a/src/plugins/unified_field_list/public/types.ts +++ b/src/plugins/unified_field_list/public/types.ts @@ -7,7 +7,6 @@ */ import type { DataViewField } from '@kbn/data-views-plugin/common'; - // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface UnifiedFieldListPluginSetup {} diff --git a/test/analytics/fixtures/plugins/analytics_ftr_helpers/public/custom_shipper.ts b/test/analytics/fixtures/plugins/analytics_ftr_helpers/public/custom_shipper.ts index 97bf37749c2561..df876da5e9cced 100644 --- a/test/analytics/fixtures/plugins/analytics_ftr_helpers/public/custom_shipper.ts +++ b/test/analytics/fixtures/plugins/analytics_ftr_helpers/public/custom_shipper.ts @@ -27,5 +27,6 @@ export class CustomShipper implements IShipper { }); } optIn(isOptedIn: boolean) {} + async flush() {} shutdown() {} } diff --git a/test/analytics/fixtures/plugins/analytics_ftr_helpers/server/custom_shipper.ts b/test/analytics/fixtures/plugins/analytics_ftr_helpers/server/custom_shipper.ts index c76f30c94572e0..c1ed593673f817 100644 --- a/test/analytics/fixtures/plugins/analytics_ftr_helpers/server/custom_shipper.ts +++ b/test/analytics/fixtures/plugins/analytics_ftr_helpers/server/custom_shipper.ts @@ -27,5 +27,6 @@ export class CustomShipper implements IShipper { }); } optIn(isOptedIn: boolean) {} + async flush() {} shutdown() {} } diff --git a/test/analytics/fixtures/plugins/analytics_plugin_a/public/custom_shipper.ts b/test/analytics/fixtures/plugins/analytics_plugin_a/public/custom_shipper.ts index bc96b9aba6afff..1bdc8b7e343fc0 100644 --- a/test/analytics/fixtures/plugins/analytics_plugin_a/public/custom_shipper.ts +++ b/test/analytics/fixtures/plugins/analytics_plugin_a/public/custom_shipper.ts @@ -39,5 +39,8 @@ export class CustomShipper implements IShipper { extendContext(newContext: EventContext) { this.actions$.next({ action: 'extendContext', meta: newContext }); } + async flush() { + this.actions$.next({ action: 'flush', meta: {} }); + } shutdown() {} } diff --git a/test/analytics/fixtures/plugins/analytics_plugin_a/public/plugin.ts b/test/analytics/fixtures/plugins/analytics_plugin_a/public/plugin.ts index 963b829f6af869..a64847e086265e 100644 --- a/test/analytics/fixtures/plugins/analytics_plugin_a/public/plugin.ts +++ b/test/analytics/fixtures/plugins/analytics_plugin_a/public/plugin.ts @@ -74,6 +74,8 @@ export class AnalyticsPluginA implements Plugin { setOptIn(optIn: boolean) { analytics.optIn({ global: { enabled: optIn } }); }, + getFlushAction: async () => + firstValueFrom(this.actions$.pipe(filter(({ action }) => action === 'flush'))), }; registerContextProvider({ diff --git a/test/analytics/fixtures/plugins/analytics_plugin_a/public/types.ts b/test/analytics/fixtures/plugins/analytics_plugin_a/public/types.ts index 197113f5368b68..42464622f131a5 100644 --- a/test/analytics/fixtures/plugins/analytics_plugin_a/public/types.ts +++ b/test/analytics/fixtures/plugins/analytics_plugin_a/public/types.ts @@ -13,6 +13,7 @@ declare global { interface Window { __analyticsPluginA__: { getActionsUntilReportTestPluginLifecycleEvent: () => Promise; + getFlushAction: () => Promise; stats: TelemetryCounter[]; setOptIn: (optIn: boolean) => void; }; diff --git a/test/analytics/fixtures/plugins/analytics_plugin_a/server/custom_shipper.ts b/test/analytics/fixtures/plugins/analytics_plugin_a/server/custom_shipper.ts index 3b91cf4b51d1b6..4c7836a811cf22 100644 --- a/test/analytics/fixtures/plugins/analytics_plugin_a/server/custom_shipper.ts +++ b/test/analytics/fixtures/plugins/analytics_plugin_a/server/custom_shipper.ts @@ -39,5 +39,8 @@ export class CustomShipper implements IShipper { extendContext(newContext: EventContext) { this.actions$.next({ action: 'extendContext', meta: newContext }); } + async flush() { + this.actions$.next({ action: 'flush', meta: {} }); + } shutdown() {} } diff --git a/test/analytics/tests/analytics_from_the_browser.ts b/test/analytics/tests/analytics_from_the_browser.ts index d38bbc42701ea0..9636e1854b0c1d 100644 --- a/test/analytics/tests/analytics_from_the_browser.ts +++ b/test/analytics/tests/analytics_from_the_browser.ts @@ -9,8 +9,8 @@ import expect from '@kbn/expect'; import type { Event, TelemetryCounter } from '@kbn/core/server'; import type { Action } from '@kbn/analytics-plugin-a-plugin/public/custom_shipper'; -import type { FtrProviderContext } from '../services'; import '@kbn/analytics-plugin-a-plugin/public/types'; +import type { FtrProviderContext } from '../services'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const { common } = getPageObjects(['common']); @@ -170,6 +170,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(event.context.user_agent).to.be.a('string'); }); + it('should call flush when using the window-exposed flush method', async () => { + await browser.execute(() => window.__kbnAnalytics.flush()); + const action = await browser.execute(() => window.__analyticsPluginA__.getFlushAction()); + expect(action).to.eql({ action: 'flush', meta: {} }); + }); + describe('Test helpers capabilities', () => { it('should return the count of the events', async () => { const eventCount = await ebtUIHelper.getEventCount({ diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index bad1f6bdebf2b4..e3a1306eaaae64 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -75,7 +75,7 @@ export class DashboardPageObject extends FtrService { public async clickFullScreenMode() { this.log.debug(`clickFullScreenMode`); await this.testSubjects.click('dashboardFullScreenMode'); - await this.testSubjects.exists('exitFullScreenModeLogo'); + await this.testSubjects.exists('exitFullScreenModeButton'); await this.waitForRenderComplete(); } @@ -99,17 +99,15 @@ export class DashboardPageObject extends FtrService { } public async exitFullScreenLogoButtonExists() { - // TODO: Replace every instance of `exitFullScreenModeLogo` with `exitFullScreenModeButton` once the new Shared UX - // full screen button can be used (i.e. after https://github.com/elastic/kibana/issues/140311 is resolved) - return await this.testSubjects.exists('exitFullScreenModeLogo'); + return await this.testSubjects.exists('exitFullScreenModeButton'); } public async getExitFullScreenLogoButton() { - return await this.testSubjects.find('exitFullScreenModeLogo'); + return await this.testSubjects.find('exitFullScreenModeButton'); } public async clickExitFullScreenLogoButton() { - await this.testSubjects.click('exitFullScreenModeLogo'); + await this.testSubjects.click('exitFullScreenModeButton'); await this.waitForRenderComplete(); } diff --git a/versions.json b/versions.json index 36212abba6deca..3fb78cfffe9571 100644 --- a/versions.json +++ b/versions.json @@ -2,17 +2,22 @@ "notice": "This file is not maintained outside of the main branch and should only be used for tooling.", "versions": [ { - "version": "8.6.0", + "version": "8.7.0", "branch": "main", "currentMajor": true, "currentMinor": true }, { - "version": "8.5.1", - "branch": "8.5", + "version": "8.6.0", + "branch": "8.6", "currentMajor": true, "previousMinor": true }, + { + "version": "8.5.2", + "branch": "8.5", + "currentMajor": true + }, { "version": "7.17.8", "branch": "7.17", diff --git a/x-pack/package.json b/x-pack/package.json index fe6050dd8f95d0..9dc04d2f20c6be 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -1,6 +1,6 @@ { "name": "x-pack", - "version": "8.6.0", + "version": "8.7.0", "author": "Elastic", "private": true, "license": "Elastic-License", diff --git a/x-pack/packages/ml/agg_utils/index.ts b/x-pack/packages/ml/agg_utils/index.ts index 444a59cbf0dc4a..c20a31f703ff45 100644 --- a/x-pack/packages/ml/agg_utils/index.ts +++ b/x-pack/packages/ml/agg_utils/index.ts @@ -5,9 +5,11 @@ * 2.0. */ +export { RANDOM_SAMPLER_SEED } from './src/constants'; export { buildSamplerAggregation } from './src/build_sampler_aggregation'; export { fetchAggIntervals } from './src/fetch_agg_intervals'; export { fetchHistogramsForFields } from './src/fetch_histograms_for_fields'; +export { getSampleProbability } from './src/get_sample_probability'; export { getSamplerAggregationsResponsePath } from './src/get_sampler_aggregations_response_path'; export { numberValidator } from './src/validate_number'; diff --git a/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.test.ts b/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.test.ts index 3da955e219ef00..cdc242cd4248e8 100644 --- a/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.test.ts +++ b/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.test.ts @@ -19,6 +19,7 @@ describe('buildRandomSamplerAggregation', () => { sample: { random_sampler: { probability: 0.01, + seed: 3867412, }, aggs: testAggs, }, diff --git a/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.ts b/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.ts index 63c048fecf42c7..c663ec8db5c54a 100644 --- a/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.ts +++ b/x-pack/packages/ml/agg_utils/src/build_random_sampler_aggregation.ts @@ -7,6 +7,8 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { RANDOM_SAMPLER_SEED } from './constants'; + /** * Wraps the supplied aggregations in a random sampler aggregation. * A supplied sample probability of 1 indicates no sampling, and the aggs are returned as-is. @@ -24,6 +26,7 @@ export function buildRandomSamplerAggregation( // @ts-expect-error `random_sampler` is not yet part of `AggregationsAggregationContainer` random_sampler: { probability: sampleProbability, + seed: RANDOM_SAMPLER_SEED, }, aggs, }, diff --git a/x-pack/packages/ml/agg_utils/src/constants.ts b/x-pack/packages/ml/agg_utils/src/constants.ts new file mode 100644 index 00000000000000..6631438a47c935 --- /dev/null +++ b/x-pack/packages/ml/agg_utils/src/constants.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. + */ + +// For the technical preview of Explain Log Rate Spikes we use a hard coded seed. +// In future versions we might use a user specific seed or let the user costumise it. +export const RANDOM_SAMPLER_SEED = 3867412; diff --git a/x-pack/packages/ml/agg_utils/src/get_sample_probability.test.ts b/x-pack/packages/ml/agg_utils/src/get_sample_probability.test.ts new file mode 100644 index 00000000000000..ce6ea9e083cbb5 --- /dev/null +++ b/x-pack/packages/ml/agg_utils/src/get_sample_probability.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getSampleProbability } from './get_sample_probability'; + +describe('getSampleProbability', () => { + test('returns sample probability of 1 for docs up to required minimum doc count', () => { + expect(getSampleProbability(0)).toEqual(1); + expect(getSampleProbability(1)).toEqual(1); + expect(getSampleProbability(10)).toEqual(1); + expect(getSampleProbability(100)).toEqual(1); + expect(getSampleProbability(1000)).toEqual(1); + expect(getSampleProbability(10000)).toEqual(1); + expect(getSampleProbability(50000)).toEqual(1); + }); + test('returns sample probability of 0.5 for docs in range 50001-100000', () => { + expect(getSampleProbability(50001)).toEqual(0.5); + expect(getSampleProbability(100000)).toEqual(0.5); + }); + test('returns sample probability based on total docs ratio', () => { + expect(getSampleProbability(100001)).toEqual(0.4999950000499995); + expect(getSampleProbability(1000000)).toEqual(0.05); + expect(getSampleProbability(1000001)).toEqual(0.04999995000005); + expect(getSampleProbability(2000000)).toEqual(0.025); + expect(getSampleProbability(5000000)).toEqual(0.01); + expect(getSampleProbability(10000000)).toEqual(0.005); + expect(getSampleProbability(100000000)).toEqual(0.0005); + expect(getSampleProbability(1000000000)).toEqual(0.00005); + expect(getSampleProbability(10000000000)).toEqual(0.000005); + }); +}); diff --git a/x-pack/packages/ml/agg_utils/src/get_sample_probability.ts b/x-pack/packages/ml/agg_utils/src/get_sample_probability.ts new file mode 100644 index 00000000000000..4f1e1be948a027 --- /dev/null +++ b/x-pack/packages/ml/agg_utils/src/get_sample_probability.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. + */ + +const SAMPLE_PROBABILITY_MIN_DOC_COUNT = 50000; + +export function getSampleProbability(totalDocCount: number) { + let sampleProbability = 1; + + if (totalDocCount > SAMPLE_PROBABILITY_MIN_DOC_COUNT) { + sampleProbability = Math.min(0.5, SAMPLE_PROBABILITY_MIN_DOC_COUNT / totalDocCount); + } + + return sampleProbability; +} diff --git a/x-pack/packages/ml/is_populated_object/src/is_populated_object.ts b/x-pack/packages/ml/is_populated_object/src/is_populated_object.ts index 43c529206bb63f..e8233b38cf356f 100644 --- a/x-pack/packages/ml/is_populated_object/src/is_populated_object.ts +++ b/x-pack/packages/ml/is_populated_object/src/is_populated_object.ts @@ -22,10 +22,10 @@ * Otherwise you'd just satisfy TS requirements but might still * run into runtime issues. */ -export const isPopulatedObject = ( +export const isPopulatedObject = ( arg: unknown, requiredAttributes: U[] = [] -): arg is Record => { +): arg is Record => { return ( typeof arg === 'object' && arg !== null && diff --git a/x-pack/plugins/aiops/common/index.ts b/x-pack/plugins/aiops/common/index.ts index 0f4835d67ecc77..0726447532aa24 100755 --- a/x-pack/plugins/aiops/common/index.ts +++ b/x-pack/plugins/aiops/common/index.ts @@ -20,3 +20,5 @@ export const PLUGIN_NAME = 'AIOps'; * "Explain log rate spikes UI" during development until the first release. */ export const AIOPS_ENABLED = true; + +export const CHANGE_POINT_DETECTION_ENABLED = false; diff --git a/x-pack/plugins/aiops/kibana.json b/x-pack/plugins/aiops/kibana.json index ce8057bc03f04e..dba431234ec0a7 100755 --- a/x-pack/plugins/aiops/kibana.json +++ b/x-pack/plugins/aiops/kibana.json @@ -12,6 +12,7 @@ "requiredPlugins": [ "charts", "data", + "lens", "licensing" ], "optionalPlugins": [], diff --git a/x-pack/plugins/aiops/public/application/utils/search_utils.ts b/x-pack/plugins/aiops/public/application/utils/search_utils.ts index 93481cbe5658db..d3643daace9427 100644 --- a/x-pack/plugins/aiops/public/application/utils/search_utils.ts +++ b/x-pack/plugins/aiops/public/application/utils/search_utils.ts @@ -204,7 +204,7 @@ export function getEsQueryFromSavedSearch({ }; } - // If saved search available, merge saved search with latest user query or filters + // If saved search available, merge saved search with the latest user query or filters // which might differ from extracted saved search data if (savedSearchData) { const globalFilters = filterManager?.getGlobalFilters(); diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx new file mode 100644 index 00000000000000..7fd586953fe96d --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx @@ -0,0 +1,275 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { + createContext, + FC, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; +import { type DataViewField } from '@kbn/data-views-plugin/public'; +import { startWith } from 'rxjs'; +import useMount from 'react-use/lib/useMount'; +import type { Query, Filter } from '@kbn/es-query'; +import { + createMergedEsQuery, + getEsQueryFromSavedSearch, +} from '../../application/utils/search_utils'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { useTimefilter, useTimeRangeUpdates } from '../../hooks/use_time_filter'; +import { useChangePointResults } from './use_change_point_agg_request'; +import { type TimeBuckets, TimeBucketsInterval } from '../../../common/time_buckets'; +import { useDataSource } from '../../hooks/use_data_source'; +import { usePageUrlState } from '../../hooks/use_url_state'; +import { useTimeBuckets } from '../../hooks/use_time_buckets'; + +export interface ChangePointDetectionRequestParams { + fn: string; + splitField: string; + metricField: string; + interval: string; + query: Query; + filters: Filter[]; +} + +export const ChangePointDetectionContext = createContext<{ + timeBuckets: TimeBuckets; + bucketInterval: TimeBucketsInterval; + requestParams: ChangePointDetectionRequestParams; + metricFieldOptions: DataViewField[]; + splitFieldsOptions: DataViewField[]; + updateRequestParams: (update: Partial) => void; + isLoading: boolean; + annotations: ChangePointAnnotation[]; + resultFilters: Filter[]; + updateFilters: (update: Filter[]) => void; + resultQuery: Query; + progress: number; + pagination: { + activePage: number; + pageCount: number; + updatePagination: (newPage: number) => void; + }; +}>({ + isLoading: false, + splitFieldsOptions: [], + metricFieldOptions: [], + requestParams: {} as ChangePointDetectionRequestParams, + timeBuckets: {} as TimeBuckets, + bucketInterval: {} as TimeBucketsInterval, + updateRequestParams: () => {}, + annotations: [], + resultFilters: [], + updateFilters: () => {}, + resultQuery: { query: '', language: 'kuery' }, + progress: 0, + pagination: { + activePage: 0, + pageCount: 1, + updatePagination: () => {}, + }, +}); + +export type ChangePointType = + | 'dip' + | 'spike' + | 'distribution_change' + | 'step_change' + | 'trend_change' + | 'stationary' + | 'non_stationary' + | 'indeterminable'; + +export interface ChangePointAnnotation { + label: string; + reason: string; + timestamp: string; + group_field: string; + type: ChangePointType; + p_value: number; +} + +const DEFAULT_AGG_FUNCTION = 'min'; + +export const ChangePointDetectionContextProvider: FC = ({ children }) => { + const { dataView, savedSearch } = useDataSource(); + const { + uiSettings, + data: { + query: { filterManager }, + }, + } = useAiopsAppContext(); + + const savedSearchQuery = useMemo(() => { + return getEsQueryFromSavedSearch({ + dataView, + uiSettings, + savedSearch, + filterManager, + }); + }, [dataView, savedSearch, uiSettings, filterManager]); + + const timefilter = useTimefilter(); + const timeBuckets = useTimeBuckets(); + const [resultFilters, setResultFilter] = useState([]); + + const [bucketInterval, setBucketInterval] = useState(); + + const timeRange = useTimeRangeUpdates(); + + useMount(function updateIntervalOnTimeBoundsChange() { + const timeUpdateSubscription = timefilter + .getTimeUpdate$() + .pipe(startWith(timefilter.getTime())) + .subscribe(() => { + const activeBounds = timefilter.getActiveBounds(); + if (!activeBounds) { + throw new Error('Time bound not available'); + } + timeBuckets.setInterval('auto'); + timeBuckets.setBounds(activeBounds); + setBucketInterval(timeBuckets.getInterval()); + }); + return () => { + timeUpdateSubscription.unsubscribe(); + }; + }); + + const metricFieldOptions = useMemo(() => { + return dataView.fields.filter(({ aggregatable, type }) => aggregatable && type === 'number'); + }, [dataView]); + + const splitFieldsOptions = useMemo(() => { + return dataView.fields.filter( + ({ aggregatable, esTypes, displayName }) => + aggregatable && + esTypes && + esTypes.includes('keyword') && + !['_id', '_index'].includes(displayName) + ); + }, [dataView]); + + const [requestParamsFromUrl, updateRequestParams] = + usePageUrlState('changePoint'); + + const resultQuery = useMemo(() => { + return ( + requestParamsFromUrl.query ?? { + query: savedSearchQuery?.searchString ?? '', + language: savedSearchQuery?.queryLanguage ?? 'kuery', + } + ); + }, [savedSearchQuery, requestParamsFromUrl.query]); + + const requestParams = useMemo(() => { + const params = { ...requestParamsFromUrl }; + if (!params.fn) { + params.fn = DEFAULT_AGG_FUNCTION; + } + if (!params.metricField && metricFieldOptions.length > 0) { + params.metricField = metricFieldOptions[0].name; + } + if (!params.splitField && splitFieldsOptions.length > 0) { + params.splitField = splitFieldsOptions[0].name; + } + params.interval = bucketInterval?.expression!; + return params; + }, [requestParamsFromUrl, metricFieldOptions, splitFieldsOptions, bucketInterval]); + + const updateFilters = useCallback( + (update: Filter[]) => { + filterManager.setFilters(update); + }, + [filterManager] + ); + + useMount(() => { + setResultFilter(filterManager.getFilters()); + const sub = filterManager.getUpdates$().subscribe(() => { + setResultFilter(filterManager.getFilters()); + }); + return () => { + sub.unsubscribe(); + }; + }); + + useEffect( + function syncFilters() { + const globalFilters = filterManager?.getGlobalFilters(); + if (requestParamsFromUrl.filters) { + filterManager.setFilters(requestParamsFromUrl.filters); + } + if (globalFilters) { + filterManager?.addFilters(globalFilters); + } + }, + [requestParamsFromUrl.filters, filterManager] + ); + + const combinedQuery = useMemo(() => { + const mergedQuery = createMergedEsQuery(resultQuery, resultFilters, dataView, uiSettings); + if (!Array.isArray(mergedQuery.bool?.filter)) { + if (!mergedQuery.bool) { + mergedQuery.bool = {}; + } + mergedQuery.bool.filter = []; + } + + mergedQuery.bool!.filter.push({ + range: { + [dataView.timeFieldName!]: { + from: timeRange.from, + to: timeRange.to, + }, + }, + }); + + return mergedQuery; + }, [resultFilters, resultQuery, uiSettings, dataView, timeRange]); + + const { + results: annotations, + isLoading: annotationsLoading, + progress, + pagination, + } = useChangePointResults(requestParams, combinedQuery); + + if (!bucketInterval) return null; + + const value = { + isLoading: annotationsLoading, + progress, + timeBuckets, + requestParams, + updateRequestParams, + metricFieldOptions, + splitFieldsOptions, + annotations, + bucketInterval, + resultFilters, + updateFilters, + resultQuery, + pagination, + }; + + return ( + + {children} + + ); +}; + +export function useChangePointDetectionContext() { + return useContext(ChangePointDetectionContext); +} + +export function useRequestParams() { + return useChangePointDetectionContext().requestParams; +} diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_page.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_page.tsx new file mode 100644 index 00000000000000..8e5c06b38f85cd --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_page.tsx @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React, { FC, useCallback } from 'react'; +import { + EuiBadge, + EuiDescriptionList, + EuiEmptyPrompt, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiPagination, + EuiPanel, + EuiProgress, + EuiSpacer, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { Query } from '@kbn/es-query'; +import { SearchBarWrapper } from './search_bar'; +import { useChangePointDetectionContext } from './change_point_detection_context'; +import { MetricFieldSelector } from './metric_field_selector'; +import { SplitFieldSelector } from './split_field_selector'; +import { FunctionPicker } from './function_picker'; +import { ChartComponent } from './chart_component'; + +export const ChangePointDetectionPage: FC = () => { + const { + requestParams, + updateRequestParams, + annotations, + resultFilters, + updateFilters, + resultQuery, + progress, + pagination, + } = useChangePointDetectionContext(); + + const setFn = useCallback( + (fn: string) => { + updateRequestParams({ fn }); + }, + [updateRequestParams] + ); + + const setSplitField = useCallback( + (splitField: string) => { + updateRequestParams({ splitField }); + }, + [updateRequestParams] + ); + + const setMetricField = useCallback( + (metricField: string) => { + updateRequestParams({ metricField }); + }, + [updateRequestParams] + ); + + const setQuery = useCallback( + (query: Query) => { + updateRequestParams({ query }); + }, + [updateRequestParams] + ); + + const selectControlCss = { width: '200px' }; + + return ( +
+ + + + + + + + + + + + + + + + + + } + value={progress} + max={100} + valueText + size="m" + /> + + + + + + + {annotations.length === 0 && progress === 100 ? ( + <> + + + + } + body={ +

+ +

+ } + /> + + ) : null} + + = 2 ? 2 : 1} responsive gutterSize={'m'}> + {annotations.map((v) => { + return ( + + + + + + + +

{v.group_field}

+
+
+ {v.reason ? ( + + + + + + ) : null} +
+
+ + {v.type} + +
+ + {v.p_value !== undefined ? ( + + ) : null} + +
+
+ ); + })} +
+ + + + {pagination.pageCount > 1 ? ( + + + + + + ) : null} +
+ ); +}; diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detetion_root.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detetion_root.tsx new file mode 100644 index 00000000000000..0b4a14928a19c2 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detetion_root.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 { DataView } from '@kbn/data-views-plugin/common'; +import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import React, { FC } from 'react'; +import { PageHeader } from '../page_header'; +import { ChangePointDetectionContextProvider } from './change_point_detection_context'; +import { DataSourceContext } from '../../hooks/use_data_source'; +import { UrlStateProvider } from '../../hooks/use_url_state'; +import { SavedSearchSavedObject } from '../../application/utils/search_utils'; +import { AiopsAppContext, AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; +import { ChangePointDetectionPage } from './change_point_detection_page'; + +export interface ChangePointDetectionAppStateProps { + dataView: DataView; + savedSearch: SavedSearch | SavedSearchSavedObject | null; + appDependencies: AiopsAppDependencies; +} + +export const ChangePointDetectionAppState: FC = ({ + dataView, + savedSearch, + appDependencies, +}) => { + return ( + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/chart_component.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/chart_component.tsx new file mode 100644 index 00000000000000..becfbd813fd4cb --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/chart_component.tsx @@ -0,0 +1,215 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useMemo } from 'react'; +import { type TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { FilterStateStore } from '@kbn/es-query'; +import { useChangePointDetectionContext } from './change_point_detection_context'; +import { useDataSource } from '../../hooks/use_data_source'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { useTimeRangeUpdates } from '../../hooks/use_time_filter'; +import { fnOperationTypeMapping } from './constants'; + +export interface ChartComponentProps { + annotation: { + group_field: string; + label: string; + timestamp: string; + reason: string; + }; +} + +export const ChartComponent: FC = React.memo(({ annotation }) => { + const { + lens: { EmbeddableComponent }, + } = useAiopsAppContext(); + + const timeRange = useTimeRangeUpdates(); + const { dataView } = useDataSource(); + const { requestParams, bucketInterval } = useChangePointDetectionContext(); + + const filters = useMemo( + () => [ + { + meta: { + index: dataView.id!, + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: requestParams.splitField, + params: { + query: annotation.group_field, + }, + }, + query: { + match_phrase: { + [requestParams.splitField]: annotation.group_field, + }, + }, + $state: { + store: FilterStateStore.APP_STATE, + }, + }, + ], + [dataView.id, requestParams.splitField, annotation.group_field] + ); + + // @ts-ignore incorrect types for attributes + const attributes = useMemo(() => { + return { + title: annotation.group_field, + description: '', + visualizationType: 'lnsXY', + type: 'lens', + references: [ + { + type: 'index-pattern', + id: dataView.id!, + name: 'indexpattern-datasource-layer-2d61a885-abb0-4d4e-a5f9-c488caec3c22', + }, + { + type: 'index-pattern', + id: dataView.id!, + name: 'xy-visualization-layer-8d26ab67-b841-4877-9d02-55bf270f9caf', + }, + ], + state: { + visualization: { + legend: { + isVisible: false, + position: 'right', + }, + valueLabels: 'hide', + fittingFunction: 'None', + axisTitlesVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + tickLabelsVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + labelsOrientation: { + x: 0, + yLeft: 0, + yRight: 0, + }, + gridlinesVisibilitySettings: { + x: true, + yLeft: true, + yRight: true, + }, + preferredSeriesType: 'line', + layers: [ + { + layerId: '2d61a885-abb0-4d4e-a5f9-c488caec3c22', + accessors: ['e9f26d17-fb36-4982-8539-03f1849cbed0'], + position: 'top', + seriesType: 'line', + showGridlines: false, + layerType: 'data', + xAccessor: '877e6638-bfaa-43ec-afb9-2241dc8e1c86', + }, + ...(annotation.timestamp + ? [ + { + layerId: '8d26ab67-b841-4877-9d02-55bf270f9caf', + layerType: 'annotations', + annotations: [ + { + type: 'manual', + label: annotation.label, + icon: 'triangle', + textVisibility: true, + key: { + type: 'point_in_time', + timestamp: annotation.timestamp, + }, + id: 'a8fb297c-8d96-4011-93c0-45af110d5302', + isHidden: false, + color: '#F04E98', + lineStyle: 'solid', + lineWidth: 2, + outside: false, + }, + ], + ignoreGlobalFilters: true, + }, + ] + : []), + ], + }, + query: { + query: '', + language: 'kuery', + }, + filters, + datasourceStates: { + formBased: { + layers: { + '2d61a885-abb0-4d4e-a5f9-c488caec3c22': { + columns: { + '877e6638-bfaa-43ec-afb9-2241dc8e1c86': { + label: dataView.timeFieldName, + dataType: 'date', + operationType: 'date_histogram', + sourceField: dataView.timeFieldName, + isBucketed: true, + scale: 'interval', + params: { + interval: bucketInterval.expression, + includeEmptyRows: true, + dropPartials: false, + }, + }, + 'e9f26d17-fb36-4982-8539-03f1849cbed0': { + label: `${requestParams.fn}(${requestParams.metricField})`, + dataType: 'number', + operationType: fnOperationTypeMapping[requestParams.fn], + sourceField: requestParams.metricField, + isBucketed: false, + scale: 'ratio', + params: { + emptyAsNull: true, + }, + }, + }, + columnOrder: [ + '877e6638-bfaa-43ec-afb9-2241dc8e1c86', + 'e9f26d17-fb36-4982-8539-03f1849cbed0', + ], + incompleteColumns: {}, + }, + }, + }, + textBased: { + layers: {}, + }, + }, + internalReferences: [], + adHocDataViews: {}, + }, + }; + }, [dataView.id, dataView.timeFieldName, annotation, requestParams, filters, bucketInterval]); + + return ( + + ); +}); diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/styles.ts b/x-pack/plugins/aiops/public/components/change_point_detection/constants.ts similarity index 59% rename from x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/styles.ts rename to x-pack/plugins/aiops/public/components/change_point_detection/constants.ts index 4f7814eb793ccb..d6bbd27858fef4 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/styles.ts +++ b/x-pack/plugins/aiops/public/components/change_point_detection/constants.ts @@ -5,14 +5,9 @@ * 2.0. */ -import { CSSObject } from '@emotion/react'; - -export const useStyles = () => { - const button: CSSObject = { - display: 'inline-flex', - }; - - return { - button, - }; -}; +export const fnOperationTypeMapping: Record = { + min: 'min', + max: 'max', + sum: 'sum', + avg: 'average', +} as const; diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/function_picker.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/function_picker.tsx new file mode 100644 index 00000000000000..52a304d85fb853 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/function_picker.tsx @@ -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 { EuiFormRow, EuiSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { FC } from 'react'; +import { fnOperationTypeMapping } from './constants'; + +interface FunctionPickerProps { + value: string; + onChange: (value: string) => void; +} + +export const FunctionPicker: FC = React.memo(({ value, onChange }) => { + const options = Object.keys(fnOperationTypeMapping).map((v) => { + return { + value: v, + text: v, + }; + }); + + return ( + + onChange(e.target.value)} + prepend={i18n.translate('xpack.aiops.changePointDetection.selectFunctionLabel', { + defaultMessage: 'Function', + })} + /> + + ); +}); diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/index.ts b/x-pack/plugins/aiops/public/components/change_point_detection/index.ts new file mode 100644 index 00000000000000..5083f19cfe39fb --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { type ChangePointDetectionAppStateProps } from './change_point_detetion_root'; + +import { ChangePointDetectionAppState } from './change_point_detetion_root'; + +// required for dynamic import using React.lazy() +// eslint-disable-next-line import/no-default-export +export default ChangePointDetectionAppState; diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/metric_field_selector.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/metric_field_selector.tsx new file mode 100644 index 00000000000000..bbdc3e5742b080 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/metric_field_selector.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, { FC, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui'; +import { useChangePointDetectionContext } from './change_point_detection_context'; + +interface MetricFieldSelectorProps { + value: string; + onChange: (value: string) => void; +} + +export const MetricFieldSelector: FC = React.memo( + ({ value, onChange }) => { + const { metricFieldOptions } = useChangePointDetectionContext(); + + const options = useMemo(() => { + return metricFieldOptions.map((v) => ({ value: v.name, text: v.displayName })); + }, [metricFieldOptions]); + + return ( + + onChange(e.target.value)} + /> + + ); + } +); diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/search_bar.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/search_bar.tsx new file mode 100644 index 00000000000000..020c5da876b2a6 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/search_bar.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { type Filter, fromKueryExpression, type Query } from '@kbn/es-query'; +import { type SearchBarOwnProps } from '@kbn/unified-search-plugin/public/search_bar'; +import { EuiSpacer, EuiTextColor } from '@elastic/eui'; +import { SEARCH_QUERY_LANGUAGE } from '../../application/utils/search_utils'; +import { useDataSource } from '../../hooks/use_data_source'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; + +export interface SearchBarProps { + query: Query; + filters: Filter[]; + onQueryChange: (update: Query) => void; + onFiltersChange: (update: Filter[]) => void; +} + +/** + * Reusable search bar component for the AIOps app. + * + * @param query + * @param filters + * @param onQueryChange + * @param onFiltersChange + * @constructor + */ +export const SearchBarWrapper: FC = ({ + query, + filters, + onQueryChange, + onFiltersChange, +}) => { + const { dataView } = useDataSource(); + const { + unifiedSearch: { + ui: { SearchBar }, + }, + } = useAiopsAppContext(); + + const [error, setError] = useState(); + + const onQuerySubmit: SearchBarOwnProps['onQuerySubmit'] = useCallback( + (payload, isUpdate) => { + if (payload.query.language === SEARCH_QUERY_LANGUAGE.KUERY) { + try { + // Validates the query + fromKueryExpression(payload.query.query); + setError(undefined); + onQueryChange(payload.query); + } catch (e) { + setError(e.message); + } + } + }, + [onQueryChange] + ); + + const onFiltersUpdated = useCallback( + (updatedFilters: Filter[]) => { + onFiltersChange(updatedFilters); + }, + [onFiltersChange] + ); + + const resultQuery = query ?? { query: '', language: 'kuery' }; + + return ( + <> + + {error ? ( + <> + + {error} + + ) : null} + + ); +}; diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/split_field_selector.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/split_field_selector.tsx new file mode 100644 index 00000000000000..1a91e69af65baf --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/split_field_selector.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, { FC, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFormRow, EuiSelect, type EuiSelectOption } from '@elastic/eui'; +import { useChangePointDetectionContext } from './change_point_detection_context'; + +interface SplitFieldSelectorProps { + value: string; + onChange: (value: string) => void; +} + +export const SplitFieldSelector: FC = React.memo(({ value, onChange }) => { + const { splitFieldsOptions } = useChangePointDetectionContext(); + + const options = useMemo(() => { + return splitFieldsOptions.map((v) => ({ value: v.name, text: v.displayName })); + }, [splitFieldsOptions]); + + return ( + + onChange(e.target.value)} + /> + + ); +}); diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/use_change_point_agg_request.ts b/x-pack/plugins/aiops/public/components/change_point_detection/use_change_point_agg_request.ts new file mode 100644 index 00000000000000..b00c5dab790b76 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/use_change_point_agg_request.ts @@ -0,0 +1,263 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, useState, useMemo } from 'react'; +import { type QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { i18n } from '@kbn/i18n'; +import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { + ChangePointAnnotation, + ChangePointDetectionRequestParams, + ChangePointType, +} from './change_point_detection_context'; +import { useDataSource } from '../../hooks/use_data_source'; +import { useCancellableSearch } from '../../hooks/use_cancellable_search'; +import { useSplitFieldCardinality } from './use_split_field_cardinality'; + +interface RequestOptions { + index: string; + fn: string; + metricField: string; + splitField: string; + timeField: string; + timeInterval: string; + afterKey?: string; +} + +export const COMPOSITE_AGG_SIZE = 500; + +function getChangePointDetectionRequestBody( + { index, fn, metricField, splitField, timeInterval, timeField, afterKey }: RequestOptions, + query: QueryDslQueryContainer +) { + return { + params: { + index, + size: 0, + body: { + query, + aggregations: { + groupings: { + composite: { + size: COMPOSITE_AGG_SIZE, + ...(afterKey !== undefined ? { after: { splitFieldTerm: afterKey } } : {}), + sources: [ + { + splitFieldTerm: { + terms: { + field: splitField, + }, + }, + }, + ], + }, + aggregations: { + over_time: { + date_histogram: { + field: timeField, + fixed_interval: timeInterval, + }, + aggs: { + function_value: { + [fn]: { + field: metricField, + }, + }, + }, + }, + change_point_request: { + change_point: { + buckets_path: 'over_time>function_value', + }, + }, + select: { + bucket_selector: { + buckets_path: { p_value: 'change_point_request.p_value' }, + script: 'params.p_value < 1', + }, + }, + sort: { + bucket_sort: { + sort: [{ 'change_point_request.p_value': { order: 'asc' } }], + }, + }, + }, + }, + }, + }, + }, + }; +} + +const CHARTS_PER_PAGE = 6; + +export function useChangePointResults( + requestParams: ChangePointDetectionRequestParams, + query: QueryDslQueryContainer +) { + const { + notifications: { toasts }, + } = useAiopsAppContext(); + + const { dataView } = useDataSource(); + + const [results, setResults] = useState([]); + const [activePage, setActivePage] = useState(0); + const [progress, setProgress] = useState(0); + + const splitFieldCardinality = useSplitFieldCardinality(requestParams.splitField, query); + + const { runRequest, cancelRequest, isLoading } = useCancellableSearch(); + + const reset = useCallback(() => { + cancelRequest(); + setProgress(0); + setActivePage(0); + setResults([]); + }, [cancelRequest]); + + const fetchResults = useCallback( + async (afterKey?: string, prevBucketsCount?: number) => { + try { + if (!splitFieldCardinality) { + setProgress(100); + return; + } + + const requestPayload = getChangePointDetectionRequestBody( + { + index: dataView.getIndexPattern(), + fn: requestParams.fn, + timeInterval: requestParams.interval, + metricField: requestParams.metricField, + timeField: dataView.timeFieldName!, + splitField: requestParams.splitField, + afterKey, + }, + query + ); + const result = await runRequest< + typeof requestPayload, + { rawResponse: ChangePointAggResponse } + >(requestPayload); + + if (result === null) { + setProgress(100); + return; + } + + const buckets = result.rawResponse.aggregations.groupings.buckets; + + setProgress( + Math.min( + Math.round(((buckets.length + (prevBucketsCount ?? 0)) / splitFieldCardinality) * 100), + 100 + ) + ); + + const groups = buckets.map((v) => { + const changePointType = Object.keys(v.change_point_request.type)[0] as ChangePointType; + const timeAsString = v.change_point_request.bucket?.key; + const rawPValue = v.change_point_request.type[changePointType].p_value; + + return { + group_field: v.key.splitFieldTerm, + type: changePointType, + p_value: rawPValue, + timestamp: timeAsString, + label: changePointType, + reason: v.change_point_request.type[changePointType].reason, + } as ChangePointAnnotation; + }); + + setResults((prev) => { + return ( + (prev ?? []) + .concat(groups) + // Lower p_value indicates a bigger change point, hence the acs sorting + .sort((a, b) => a.p_value - b.p_value) + ); + }); + + if (result.rawResponse.aggregations.groupings.after_key?.splitFieldTerm) { + await fetchResults( + result.rawResponse.aggregations.groupings.after_key.splitFieldTerm, + buckets.length + (prevBucketsCount ?? 0) + ); + } else { + setProgress(100); + } + } catch (e) { + toasts.addError(e, { + title: i18n.translate('xpack.aiops.changePointDetection.fetchErrorTitle', { + defaultMessage: 'Failed to fetch change points', + }), + }); + } + }, + [runRequest, requestParams, query, dataView, splitFieldCardinality, toasts] + ); + + useEffect( + function fetchResultsOnInputChange() { + reset(); + fetchResults(); + + return () => { + cancelRequest(); + }; + }, + [requestParams, query, splitFieldCardinality, fetchResults, reset, cancelRequest] + ); + + const pagination = useMemo(() => { + return { + activePage, + pageCount: Math.round((results.length ?? 0) / CHARTS_PER_PAGE), + updatePagination: setActivePage, + }; + }, [activePage, results.length]); + + const resultPerPage = useMemo(() => { + const start = activePage * CHARTS_PER_PAGE; + return results.slice(start, start + CHARTS_PER_PAGE); + }, [results, activePage]); + + return { results: resultPerPage, isLoading, reset, progress, pagination }; +} + +interface ChangePointAggResponse { + took: number; + timed_out: boolean; + _shards: { total: number; failed: number; successful: number; skipped: number }; + hits: { hits: any[]; total: number; max_score: null }; + aggregations: { + groupings: { + after_key?: { + splitFieldTerm: string; + }; + buckets: Array<{ + key: { splitFieldTerm: string }; + doc_count: number; + over_time: { + buckets: Array<{ + key_as_string: string; + doc_count: number; + function_value: { value: number }; + key: number; + }>; + }; + change_point_request: { + bucket?: { doc_count: number; function_value: { value: number }; key: string }; + type: { + [key in ChangePointType]: { p_value: number; change_point: number; reason?: string }; + }; + }; + }>; + }; + }; +} diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/use_split_field_cardinality.ts b/x-pack/plugins/aiops/public/components/change_point_detection/use_split_field_cardinality.ts new file mode 100644 index 00000000000000..19e8212f1b8a8b --- /dev/null +++ b/x-pack/plugins/aiops/public/components/change_point_detection/use_split_field_cardinality.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useMemo, useState } from 'react'; +import type { + QueryDslQueryContainer, + AggregationsCardinalityAggregate, + SearchResponseBody, +} from '@elastic/elasticsearch/lib/api/types'; +import { useCancellableSearch } from '../../hooks/use_cancellable_search'; +import { useDataSource } from '../../hooks/use_data_source'; + +/** + * Gets the cardinality of the selected split field + * @param splitField + * @param query + */ +export function useSplitFieldCardinality(splitField: string, query: QueryDslQueryContainer) { + const [cardinality, setCardinality] = useState(); + const { dataView } = useDataSource(); + + const requestPayload = useMemo(() => { + return { + params: { + index: dataView.getIndexPattern(), + size: 0, + body: { + query, + aggregations: { + fieldCount: { + cardinality: { + field: splitField, + }, + }, + }, + }, + }, + }; + }, [splitField, dataView, query]); + + const { runRequest: getSplitFieldCardinality, cancelRequest } = useCancellableSearch(); + + useEffect( + function performCardinalityCheck() { + cancelRequest(); + + getSplitFieldCardinality< + typeof requestPayload, + { + rawResponse: SearchResponseBody< + unknown, + { fieldCount: AggregationsCardinalityAggregate } + >; + } + >(requestPayload).then((response) => { + if (response?.rawResponse.aggregations) { + setCardinality(response.rawResponse.aggregations.fieldCount.value); + } + }); + }, + [getSplitFieldCardinality, requestPayload, cancelRequest] + ); + + return cardinality; +} diff --git a/x-pack/plugins/aiops/public/components/date_picker_wrapper/date_picker_wrapper.tsx b/x-pack/plugins/aiops/public/components/date_picker_wrapper/date_picker_wrapper.tsx index 6b3e57200b90b3..566d3b6ae7b5bc 100644 --- a/x-pack/plugins/aiops/public/components/date_picker_wrapper/date_picker_wrapper.tsx +++ b/x-pack/plugins/aiops/public/components/date_picker_wrapper/date_picker_wrapper.tsx @@ -21,13 +21,12 @@ import { OnTimeChangeProps, } from '@elastic/eui'; import type { TimeRange } from '@kbn/es-query'; -import { TimefilterContract, TimeHistoryContract, UI_SETTINGS } from '@kbn/data-plugin/public'; +import { TimeHistoryContract, UI_SETTINGS } from '@kbn/data-plugin/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import useObservable from 'react-use/lib/useObservable'; -import { map } from 'rxjs/operators'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; +import { useRefreshIntervalUpdates, useTimeRangeUpdates } from '../../hooks/use_time_filter'; import { useUrlState } from '../../hooks/use_url_state'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { aiopsRefresh$ } from '../../application/services/timefilter_refresh_service'; @@ -67,21 +66,6 @@ function updateLastRefresh(timeRange?: OnRefreshProps) { aiopsRefresh$.next({ lastRefresh: Date.now(), timeRange }); } -export const useRefreshIntervalUpdates = (timefilter: TimefilterContract) => { - return useObservable( - timefilter.getRefreshIntervalUpdate$().pipe(map(timefilter.getRefreshInterval)), - timefilter.getRefreshInterval() - ); -}; - -export const useTimeRangeUpdates = (timefilter: TimefilterContract, absolute = false) => { - const getTimeCallback = absolute - ? timefilter.getAbsoluteTime.bind(timefilter) - : timefilter.getTime.bind(timefilter); - - return useObservable(timefilter.getTimeUpdate$().pipe(map(getTimeCallback)), getTimeCallback()); -}; - export const DatePickerWrapper: FC = () => { const services = useAiopsAppContext(); const { toasts } = services.notifications; @@ -93,8 +77,8 @@ export const DatePickerWrapper: FC = () => { const [globalState, setGlobalState] = useUrlState('_g'); const getRecentlyUsedRanges = getRecentlyUsedRangesFactory(history); - const timeFilterRefreshInterval = useRefreshIntervalUpdates(timefilter); - const time = useTimeRangeUpdates(timefilter); + const timeFilterRefreshInterval = useRefreshIntervalUpdates(); + const time = useTimeRangeUpdates(); useEffect( function syncTimRangeFromUrlState() { @@ -257,13 +241,10 @@ export const DatePickerWrapper: FC = () => { } return isAutoRefreshSelectorEnabled || isTimeRangeSelectorEnabled ? ( - - + + void; + callback?: (a: GetTimeFieldRangeResponse) => void; } const FROZEN_TIER_PREFERENCE = { @@ -44,7 +47,7 @@ const FROZEN_TIER_PREFERENCE = { type FrozenTierPreference = typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE]; -export const FullTimeRangeSelector: FC = ({ +export const FullTimeRangeSelector: FC = ({ timefilter, dataView, query, diff --git a/x-pack/plugins/aiops/public/components/page_header/index.ts b/x-pack/plugins/aiops/public/components/page_header/index.ts new file mode 100644 index 00000000000000..a156a49389e9bb --- /dev/null +++ b/x-pack/plugins/aiops/public/components/page_header/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 { PageHeader } from './page_header'; diff --git a/x-pack/plugins/aiops/public/components/page_header/page_header.tsx b/x-pack/plugins/aiops/public/components/page_header/page_header.tsx new file mode 100644 index 00000000000000..fb45adcc3cde08 --- /dev/null +++ b/x-pack/plugins/aiops/public/components/page_header/page_header.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useCallback } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiPageContentHeader_Deprecated as EuiPageContentHeader, + EuiPageContentHeaderSection_Deprecated as EuiPageContentHeaderSection, +} from '@elastic/eui'; +import { FullTimeRangeSelectorProps } from '../full_time_range_selector/full_time_range_selector'; +import { useUrlState } from '../../hooks/use_url_state'; +import { useDataSource } from '../../hooks/use_data_source'; +import { useTimefilter } from '../../hooks/use_time_filter'; +import { FullTimeRangeSelector } from '../full_time_range_selector'; +import { DatePickerWrapper } from '../date_picker_wrapper'; + +export const PageHeader: FC = () => { + const [, setGlobalState] = useUrlState('_g'); + const { dataView } = useDataSource(); + + const timefilter = useTimefilter({ + timeRangeSelector: dataView.timeFieldName !== undefined, + autoRefreshSelector: true, + }); + + const updateTimeState: FullTimeRangeSelectorProps['callback'] = useCallback( + (update) => { + setGlobalState({ time: { from: update.start.string, to: update.end.string } }); + }, + [setGlobalState] + ); + + return ( + <> + + + + +
+ +

{dataView.getName()}

+
+
+
+ + + {dataView.timeFieldName !== undefined && ( + + + + )} + + + + +
+
+
+ + + ); +}; diff --git a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts index ddbfca3eb8b11a..84a0e7283c7c19 100644 --- a/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts +++ b/x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts @@ -16,6 +16,7 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { CoreStart, CoreSetup, HttpStart, IUiSettingsClient } from '@kbn/core/public'; import type { ThemeServiceStart } from '@kbn/core/public'; +import type { LensPublicStart } from '@kbn/lens-plugin/public'; export interface AiopsAppDependencies { application: CoreStart['application']; @@ -29,6 +30,7 @@ export interface AiopsAppDependencies { uiSettings: IUiSettingsClient; unifiedSearch: UnifiedSearchPublicPluginStart; share: SharePluginStart; + lens: LensPublicStart; } export const AiopsAppContext = createContext(undefined); diff --git a/x-pack/plugins/aiops/public/hooks/use_cancellable_search.ts b/x-pack/plugins/aiops/public/hooks/use_cancellable_search.ts new file mode 100644 index 00000000000000..926189a84146b8 --- /dev/null +++ b/x-pack/plugins/aiops/public/hooks/use_cancellable_search.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 { useCallback, useRef, useState } from 'react'; +import { + type IKibanaSearchResponse, + isCompleteResponse, + isErrorResponse, +} from '@kbn/data-plugin/common'; +import { tap } from 'rxjs/operators'; +import { useAiopsAppContext } from './use_aiops_app_context'; + +export function useCancellableSearch() { + const { data } = useAiopsAppContext(); + const abortController = useRef(new AbortController()); + const [isLoading, setIsFetching] = useState(false); + + const runRequest = useCallback( + ( + requestBody: RequestBody + ): Promise => { + return new Promise((resolve, reject) => { + data.search + .search(requestBody, { + abortSignal: abortController.current.signal, + }) + .pipe( + tap(() => { + setIsFetching(true); + }) + ) + .subscribe({ + next: (result) => { + if (isCompleteResponse(result)) { + setIsFetching(false); + resolve(result); + } else if (isErrorResponse(result)) { + reject(result); + } else { + // partial results + // Ignore partial results for now. + // An issue with the search function means partial results are not being returned correctly. + } + }, + error: (error) => { + if (error.name === 'AbortError') { + return resolve(null); + } + reject(error); + }, + }); + }); + }, + [data.search] + ); + + const cancelRequest = useCallback(() => { + abortController.current.abort(); + abortController.current = new AbortController(); + }, []); + + return { runRequest, cancelRequest, isLoading }; +} diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index 11977c25430017..75554d5a9b96ed 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -9,12 +9,11 @@ import { useEffect, useMemo, useState } from 'react'; import { merge } from 'rxjs'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { UI_SETTINGS } from '@kbn/data-plugin/common'; import type { ChangePoint } from '@kbn/ml-agg-utils'; import type { SavedSearch } from '@kbn/discover-plugin/public'; -import { TimeBuckets } from '../../common/time_buckets'; +import { useTimeBuckets } from './use_time_buckets'; import { useAiopsAppContext } from './use_aiops_app_context'; import { aiopsRefresh$ } from '../application/services/timefilter_refresh_service'; @@ -96,14 +95,7 @@ export const useData = ( lastRefresh, ]); - const _timeBuckets = useMemo(() => { - return new TimeBuckets({ - [UI_SETTINGS.HISTOGRAM_MAX_BARS]: uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS), - [UI_SETTINGS.HISTOGRAM_BAR_TARGET]: uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), - dateFormat: uiSettings.get('dateFormat'), - 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), - }); - }, [uiSettings]); + const _timeBuckets = useTimeBuckets(); const timefilter = useTimefilter({ timeRangeSelector: currentDataView?.timeFieldName !== undefined, diff --git a/x-pack/plugins/aiops/public/hooks/use_data_source.ts b/x-pack/plugins/aiops/public/hooks/use_data_source.ts new file mode 100644 index 00000000000000..2e887830f70425 --- /dev/null +++ b/x-pack/plugins/aiops/public/hooks/use_data_source.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 { createContext, useContext } from 'react'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import { SavedSearchSavedObject } from '../application/utils/search_utils'; + +export const DataSourceContext = createContext<{ + dataView: DataView | never; + savedSearch: SavedSearch | SavedSearchSavedObject | null; +}>({ + get dataView(): never { + throw new Error('Context is not implemented'); + }, + savedSearch: null, +}); + +export function useDataSource() { + return useContext(DataSourceContext); +} diff --git a/x-pack/plugins/aiops/public/hooks/use_time_buckets.ts b/x-pack/plugins/aiops/public/hooks/use_time_buckets.ts new file mode 100644 index 00000000000000..345b7d949ab7c7 --- /dev/null +++ b/x-pack/plugins/aiops/public/hooks/use_time_buckets.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 { useMemo } from 'react'; +import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { TimeBuckets } from '../../common/time_buckets'; +import { useAiopsAppContext } from './use_aiops_app_context'; + +export const useTimeBuckets = () => { + const { uiSettings } = useAiopsAppContext(); + + return useMemo(() => { + return new TimeBuckets({ + [UI_SETTINGS.HISTOGRAM_MAX_BARS]: uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS), + [UI_SETTINGS.HISTOGRAM_BAR_TARGET]: uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), + dateFormat: uiSettings.get('dateFormat'), + 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), + }); + }, [uiSettings]); +}; diff --git a/x-pack/plugins/aiops/public/hooks/use_time_filter.ts b/x-pack/plugins/aiops/public/hooks/use_time_filter.ts index 4f2a33966f71e5..c6026f9f38a55b 100644 --- a/x-pack/plugins/aiops/public/hooks/use_time_filter.ts +++ b/x-pack/plugins/aiops/public/hooks/use_time_filter.ts @@ -5,7 +5,10 @@ * 2.0. */ -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { isEqual } from 'lodash'; import { useAiopsAppContext } from './use_aiops_app_context'; interface UseTimefilterOptions { @@ -41,3 +44,31 @@ export const useTimefilter = ({ return timefilter; }; + +export const useRefreshIntervalUpdates = () => { + const timefilter = useTimefilter(); + + const refreshIntervalObservable$ = useMemo( + () => timefilter.getRefreshIntervalUpdate$().pipe(map(timefilter.getRefreshInterval)), + [timefilter] + ); + + return useObservable(refreshIntervalObservable$, timefilter.getRefreshInterval()); +}; + +export const useTimeRangeUpdates = (absolute = false) => { + const timefilter = useTimefilter(); + + const getTimeCallback = useMemo(() => { + return absolute + ? timefilter.getAbsoluteTime.bind(timefilter) + : timefilter.getTime.bind(timefilter); + }, [absolute, timefilter]); + + const timeChangeObservable$ = useMemo( + () => timefilter.getTimeUpdate$().pipe(map(getTimeCallback), distinctUntilChanged(isEqual)), + [timefilter, getTimeCallback] + ); + + return useObservable(timeChangeObservable$, getTimeCallback()); +}; diff --git a/x-pack/plugins/aiops/public/hooks/use_url_state.tsx b/x-pack/plugins/aiops/public/hooks/use_url_state.tsx index e6564ac72f9bf8..c4d84163f887e6 100644 --- a/x-pack/plugins/aiops/public/hooks/use_url_state.tsx +++ b/x-pack/plugins/aiops/public/hooks/use_url_state.tsx @@ -184,12 +184,12 @@ export const useUrlState = (accessor: Accessor) => { }; export const AppStateKey = 'AIOPS_INDEX_VIEWER'; - +export const ChangePointStateKey = 'changePoint' as const; /** * Hook for managing the URL state of the page. */ export const usePageUrlState = ( - pageKey: typeof AppStateKey, + pageKey: typeof AppStateKey | typeof ChangePointStateKey, defaultState?: PageUrlState ): [PageUrlState, (update: Partial, replaceState?: boolean) => void] => { const [appState, setAppState] = useUrlState('_a'); diff --git a/x-pack/plugins/aiops/public/index.ts b/x-pack/plugins/aiops/public/index.ts index 3cd151ea2b72f8..74a70f0acfd27d 100755 --- a/x-pack/plugins/aiops/public/index.ts +++ b/x-pack/plugins/aiops/public/index.ts @@ -13,4 +13,8 @@ export function plugin() { return new AiopsPlugin(); } -export { ExplainLogRateSpikes, LogCategorization } from './shared_lazy_components'; +export { + ExplainLogRateSpikes, + LogCategorization, + ChangePointDetection, +} from './shared_lazy_components'; diff --git a/x-pack/plugins/aiops/public/shared_lazy_components.tsx b/x-pack/plugins/aiops/public/shared_lazy_components.tsx index 90d01f999a8b65..a5ad80bd7587ed 100644 --- a/x-pack/plugins/aiops/public/shared_lazy_components.tsx +++ b/x-pack/plugins/aiops/public/shared_lazy_components.tsx @@ -15,7 +15,7 @@ const ExplainLogRateSpikesAppStateLazy = React.lazy( () => import('./components/explain_log_rate_spikes') ); -const ExplainLogRateSpikesLazyWrapper: FC = ({ children }) => ( +const LazyWrapper: FC = ({ children }) => ( }>{children} @@ -26,25 +26,30 @@ const ExplainLogRateSpikesLazyWrapper: FC = ({ children }) => ( * @param {ExplainLogRateSpikesAppStateProps} props - properties specifying the data on which to run the analysis. */ export const ExplainLogRateSpikes: FC = (props) => ( - + - + ); const LogCategorizationAppStateLazy = React.lazy(() => import('./components/log_categorization')); -const LogCategorizationLazyWrapper: FC = ({ children }) => ( - - }>{children} - -); - /** * Lazy-wrapped LogCategorizationAppStateProps React component * @param {LogCategorizationAppStateProps} props - properties specifying the data on which to run the analysis. */ export const LogCategorization: FC = (props) => ( - + - + +); + +const ChangePointDetectionLazy = React.lazy(() => import('./components/change_point_detection')); +/** + * Lazy-wrapped LogCategorizationAppStateProps React component + * @param {LogCategorizationAppStateProps} props - properties specifying the data on which to run the analysis. + */ +export const ChangePointDetection: FC = (props) => ( + + + ); 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 6400cc08ca4db8..65ac7e648eec21 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 @@ -9,7 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; -import { ChangePoint } from '@kbn/ml-agg-utils'; +import { type ChangePoint, RANDOM_SAMPLER_SEED } from '@kbn/ml-agg-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { SPIKE_ANALYSIS_THRESHOLD } from '../../../common/constants'; import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes'; @@ -85,6 +85,7 @@ export const getChangePointRequest = ( // @ts-expect-error `random_sampler` is not yet part of `AggregationsAggregationContainer` random_sampler: { probability: sampleProbability, + seed: RANDOM_SAMPLER_SEED, }, aggs: pValueAgg, }, 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 da4b8bbe5e7926..ff1fba16f28f24 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 @@ -11,7 +11,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { Logger } from '@kbn/logging'; -import type { ChangePoint, FieldValuePair } from '@kbn/ml-agg-utils'; +import { type ChangePoint, type FieldValuePair, RANDOM_SAMPLER_SEED } from '@kbn/ml-agg-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; const FREQUENT_ITEMS_FIELDS_LIMIT = 15; @@ -127,6 +127,7 @@ export async function fetchFrequentItems( // @ts-expect-error `random_sampler` is not yet part of `AggregationsAggregationContainer` random_sampler: { probability: sampleProbability, + seed: RANDOM_SAMPLER_SEED, }, aggs: frequentItemsAgg, }, diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.ts index f1444ef5972b21..93378911c7201d 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_index_info.ts @@ -8,8 +8,8 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ES_FIELD_TYPES } from '@kbn/field-types'; - import type { ElasticsearchClient } from '@kbn/core/server'; +import { getSampleProbability } from '@kbn/ml-agg-utils'; import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes'; @@ -20,7 +20,6 @@ import { getRequestBase } from './get_request_base'; // `x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts` const POPULATED_DOC_COUNT_SAMPLE_SIZE = 1000; -const SAMPLE_PROBABILITY_MIN_DOC_COUNT = 50000; const SUPPORTED_ES_FIELD_TYPES = [ ES_FIELD_TYPES.KEYWORD, @@ -96,12 +95,7 @@ export const fetchIndexInfo = async ( }); const totalDocCount = (resp.hits.total as estypes.SearchTotalHits).value; - - let sampleProbability = 1; - - if (totalDocCount > SAMPLE_PROBABILITY_MIN_DOC_COUNT) { - sampleProbability = Math.min(0.5, SAMPLE_PROBABILITY_MIN_DOC_COUNT / totalDocCount); - } + const sampleProbability = getSampleProbability(totalDocCount); return { fieldCandidates: [...finalFieldCandidates], sampleProbability, totalDocCount }; }; diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index d528e4fa3642d1..0f8ba148324bdc 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -25,5 +25,6 @@ { "path": "../security/tsconfig.json" }, { "path": "../../../src/plugins/charts/tsconfig.json" }, { "path": "../../../src/plugins/discover/tsconfig.json" }, + { "path": "../lens/tsconfig.json" } ] } diff --git a/x-pack/plugins/alerting/common/alert_summary.ts b/x-pack/plugins/alerting/common/alert_summary.ts index f9675e64a7f959..25b00538c16bee 100644 --- a/x-pack/plugins/alerting/common/alert_summary.ts +++ b/x-pack/plugins/alerting/common/alert_summary.ts @@ -20,7 +20,7 @@ export interface AlertSummary { ruleTypeId: string; consumer: string; muteAll: boolean; - throttle: string | null; + throttle?: string | null; enabled: boolean; statusStartDate: string; statusEndDate: string; diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index bd479b96f9b1d5..b13c996e5bf780 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -75,6 +75,11 @@ export interface RuleAction { id: string; actionTypeId: string; params: RuleActionParams; + frequency?: { + summary: boolean; + notifyWhen: RuleNotifyWhenType; + throttle: string | null; + }; } export interface RuleAggregations { @@ -123,9 +128,9 @@ export interface Rule { updatedAt: Date; apiKey: string | null; apiKeyOwner: string | null; - throttle: string | null; + throttle?: string | null; muteAll: boolean; - notifyWhen: RuleNotifyWhenType | null; + notifyWhen?: RuleNotifyWhenType | null; mutedInstanceIds: string[]; executionStatus: RuleExecutionStatus; monitoring?: RuleMonitoring; diff --git a/x-pack/plugins/alerting/common/rule_notify_when_type.ts b/x-pack/plugins/alerting/common/rule_notify_when_type.ts index 700c87acdbdbbb..4be2e35f2d3926 100644 --- a/x-pack/plugins/alerting/common/rule_notify_when_type.ts +++ b/x-pack/plugins/alerting/common/rule_notify_when_type.ts @@ -5,7 +5,7 @@ * 2.0. */ -const RuleNotifyWhenTypeValues = [ +export const RuleNotifyWhenTypeValues = [ 'onActionGroupChange', 'onActiveAlert', 'onThrottleInterval', diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts index 2b403684c8d536..403826dc4668d7 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts @@ -52,6 +52,7 @@ export enum WriteOperations { Snooze = 'snooze', BulkEdit = 'bulkEdit', BulkDelete = 'bulkDelete', + BulkEnable = 'bulkEnable', Unsnooze = 'unsnooze', } diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts index f1aedf078800fd..12ea8df57eb8ed 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts @@ -31,7 +31,7 @@ export function alertSummaryFromEventLog(params: AlertSummaryFromEventLogParams) statusEndDate: dateEnd, status: 'OK', muteAll: rule.muteAll, - throttle: rule.throttle, + throttle: rule.throttle ?? null, enabled: rule.enabled, lastRun: undefined, errorMessages: [], diff --git a/x-pack/plugins/alerting/server/routes/bulk_enable_rules.test.ts b/x-pack/plugins/alerting/server/routes/bulk_enable_rules.test.ts new file mode 100644 index 00000000000000..6ea34aed9a240d --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/bulk_enable_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 { bulkEnableRulesRoute } from './bulk_enable_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('bulkEnableRulesRoute', () => { + const bulkEnableRequest = { filter: '' }; + const bulkEnableResult = { errors: [], total: 1, taskIdsFailedToBeEnabled: [] }; + + it('should enable rules with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + bulkEnableRulesRoute({ router, licenseState }); + + const [config, handler] = router.patch.mock.calls[0]; + + expect(config.path).toBe('/internal/alerting/rules/_bulk_enable'); + + rulesClient.bulkEnableRules.mockResolvedValueOnce(bulkEnableResult); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: bulkEnableRequest, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ + body: bulkEnableResult, + }); + + expect(rulesClient.bulkEnableRules).toHaveBeenCalledTimes(1); + expect(rulesClient.bulkEnableRules.mock.calls[0]).toEqual([bulkEnableRequest]); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('ensures the license allows bulk enabling rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + rulesClient.bulkEnableRules.mockResolvedValueOnce(bulkEnableResult); + + bulkEnableRulesRoute({ router, licenseState }); + + const [, handler] = router.patch.mock.calls[0]; + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: bulkEnableRequest, + } + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents bulk enabling rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('Failure'); + }); + + bulkEnableRulesRoute({ router, licenseState }); + + const [, handler] = router.patch.mock.calls[0]; + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: bulkEnableRequest, + } + ); + + 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(); + + bulkEnableRulesRoute({ router, licenseState }); + + const [, handler] = router.patch.mock.calls[0]; + + rulesClient.bulkEnableRules.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_enable_rules.ts b/x-pack/plugins/alerting/server/routes/bulk_enable_rules.ts new file mode 100644 index 00000000000000..50b35c3b16cb34 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/bulk_enable_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 bulkEnableRulesRoute = ({ + router, + licenseState, +}: { + router: IRouter; + licenseState: ILicenseState; +}) => { + router.patch( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_bulk_enable`, + 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.bulkEnableRules({ 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/clone_rule.test.ts b/x-pack/plugins/alerting/server/routes/clone_rule.test.ts new file mode 100644 index 00000000000000..7b89f217f9c8fb --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/clone_rule.test.ts @@ -0,0 +1,214 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { pick } from 'lodash'; +import { httpServiceMock } from '@kbn/core/server/mocks'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib/license_api_access'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { rulesClientMock } from '../rules_client.mock'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; +import { cloneRuleRoute } from './clone_rule'; +import { SanitizedRule } from '../types'; +import { AsApiContract } from './lib'; +import { CreateOptions } from '../rules_client'; + +const rulesClient = rulesClientMock.create(); +jest.mock('../lib/license_api_access', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('cloneRuleRoute', () => { + const createdAt = new Date(); + const updatedAt = new Date(); + + const mockedRule: SanitizedRule<{ bar: boolean }> = { + alertTypeId: '1', + consumer: 'bar', + name: 'abc', + schedule: { interval: '10s' }, + tags: ['foo'], + params: { + bar: true, + }, + throttle: '30s', + actions: [ + { + actionTypeId: 'test', + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + ], + enabled: true, + muteAll: false, + createdBy: '', + updatedBy: '', + apiKeyOwner: '', + mutedInstanceIds: [], + notifyWhen: 'onActionGroupChange', + createdAt, + updatedAt, + id: '123', + executionStatus: { + status: 'unknown', + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + }; + + const ruleToClone: AsApiContract['data']> = { + ...pick(mockedRule, 'consumer', 'name', 'schedule', 'tags', 'params', 'throttle', 'enabled'), + rule_type_id: mockedRule.alertTypeId, + notify_when: mockedRule.notifyWhen, + actions: [ + { + group: mockedRule.actions[0].group, + id: mockedRule.actions[0].id, + params: mockedRule.actions[0].params, + }, + ], + }; + + const cloneResult: AsApiContract> = { + ...ruleToClone, + mute_all: mockedRule.muteAll, + created_by: mockedRule.createdBy, + updated_by: mockedRule.updatedBy, + api_key_owner: mockedRule.apiKeyOwner, + muted_alert_ids: mockedRule.mutedInstanceIds, + created_at: mockedRule.createdAt, + updated_at: mockedRule.updatedAt, + id: mockedRule.id, + execution_status: { + status: mockedRule.executionStatus.status, + last_execution_date: mockedRule.executionStatus.lastExecutionDate, + }, + actions: [ + { + ...ruleToClone.actions[0], + connector_type_id: 'test', + }, + ], + }; + + it('clone a rule with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + cloneRuleRoute(router, licenseState); + + const [config, handler] = router.post.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/rule/{id}/_clone/{newId?}"`); + + rulesClient.clone.mockResolvedValueOnce(mockedRule); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + params: { + id: '1', + }, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ body: cloneResult }); + + expect(rulesClient.clone).toHaveBeenCalledTimes(1); + expect(rulesClient.clone.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "1", + Object { + "newId": undefined, + }, + ] + `); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('ensures the license allows updating rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + cloneRuleRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + rulesClient.clone.mockResolvedValueOnce(mockedRule); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + params: { + id: '1', + }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents updating rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + cloneRuleRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + rulesClient.clone.mockResolvedValueOnce(mockedRule); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + params: { + id: '1', + }, + }, + ['ok'] + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + cloneRuleRoute(router, licenseState); + + const [, handler] = router.post.mock.calls[0]; + + rulesClient.clone.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/clone_rule.ts b/x-pack/plugins/alerting/server/routes/clone_rule.ts new file mode 100644 index 00000000000000..3e57b4aa1c1220 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/clone_rule.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 { schema } from '@kbn/config-schema'; +import { IRouter } from '@kbn/core/server'; +import { ILicenseState, RuleTypeDisabledError } from '../lib'; +import { + verifyAccessAndContext, + RewriteResponseCase, + handleDisabledApiKeysError, + rewriteRuleLastRun, +} from './lib'; +import { + RuleTypeParams, + AlertingRequestHandlerContext, + INTERNAL_BASE_ALERTING_API_PATH, + PartialRule, +} from '../types'; + +const paramSchema = schema.object({ + id: schema.string(), + newId: schema.maybe(schema.string()), +}); + +const rewriteBodyRes: RewriteResponseCase> = ({ + actions, + alertTypeId, + scheduledTaskId, + createdBy, + updatedBy, + createdAt, + updatedAt, + apiKeyOwner, + notifyWhen, + muteAll, + mutedInstanceIds, + executionStatus, + snoozeSchedule, + isSnoozedUntil, + lastRun, + nextRun, + ...rest +}) => ({ + ...rest, + api_key_owner: apiKeyOwner, + created_by: createdBy, + updated_by: updatedBy, + snooze_schedule: snoozeSchedule, + ...(isSnoozedUntil ? { is_snoozed_until: isSnoozedUntil } : {}), + ...(alertTypeId ? { rule_type_id: alertTypeId } : {}), + ...(scheduledTaskId ? { scheduled_task_id: scheduledTaskId } : {}), + ...(createdAt ? { created_at: createdAt } : {}), + ...(updatedAt ? { updated_at: updatedAt } : {}), + ...(notifyWhen ? { notify_when: notifyWhen } : {}), + ...(muteAll !== undefined ? { mute_all: muteAll } : {}), + ...(mutedInstanceIds ? { muted_alert_ids: mutedInstanceIds } : {}), + ...(executionStatus + ? { + execution_status: { + status: executionStatus.status, + last_execution_date: executionStatus.lastExecutionDate, + last_duration: executionStatus.lastDuration, + }, + } + : {}), + ...(actions + ? { + actions: actions.map(({ group, id, actionTypeId, params }) => ({ + group, + id, + params, + connector_type_id: actionTypeId, + })), + } + : {}), + ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), + ...(nextRun ? { next_run: nextRun } : {}), +}); + +export const cloneRuleRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_clone/{newId?}`, + validate: { + params: paramSchema, + }, + }, + handleDisabledApiKeysError( + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const rulesClient = (await context.alerting).getRulesClient(); + const { id, newId } = req.params; + try { + const cloneRule = await rulesClient.clone(id, { newId }); + return res.ok({ + body: rewriteBodyRes(cloneRule), + }); + } catch (e) { + if (e instanceof RuleTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/create_rule.ts b/x-pack/plugins/alerting/server/routes/create_rule.ts index 1b114dc54d26b6..8d418f8497cb37 100644 --- a/x-pack/plugins/alerting/server/routes/create_rule.ts +++ b/x-pack/plugins/alerting/server/routes/create_rule.ts @@ -11,9 +11,11 @@ import { CreateOptions } from '../rules_client'; import { RewriteRequestCase, RewriteResponseCase, + rewriteActions, handleDisabledApiKeysError, verifyAccessAndContext, countUsageOfPredefinedIds, + actionsSchema, rewriteRuleLastRun, } from './lib'; import { @@ -31,20 +33,13 @@ export const bodySchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), consumer: schema.string(), tags: schema.arrayOf(schema.string(), { defaultValue: [] }), - throttle: schema.nullable(schema.string({ validate: validateDurationSchema })), + throttle: schema.nullable(schema.maybe(schema.string({ validate: validateDurationSchema }))), params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), schedule: schema.object({ interval: schema.string({ validate: validateDurationSchema }), }), - actions: schema.arrayOf( - schema.object({ - group: schema.string(), - id: schema.string(), - params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), - }), - { defaultValue: [] } - ), - notify_when: schema.string({ validate: validateNotifyWhenType }), + actions: actionsSchema, + notify_when: schema.maybe(schema.string({ validate: validateNotifyWhenType })), }); const rewriteBodyReq: RewriteRequestCase['data']> = ({ @@ -56,6 +51,7 @@ const rewriteBodyReq: RewriteRequestCase['data']> alertTypeId, notifyWhen, }); + const rewriteBodyRes: RewriteResponseCase> = ({ actions, alertTypeId, @@ -132,6 +128,7 @@ export const createRuleRoute = ({ router, licenseState, usageCounter }: RouteOpt await rulesClient.create({ data: rewriteBodyReq({ ...rule, + actions: rewriteActions(rule.actions), notify_when: rule.notify_when as RuleNotifyWhenType, }), options: { id: params?.id }, diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index dfb9c290af3d26..f5f79ef7cd573d 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -39,6 +39,8 @@ import { snoozeRuleRoute } from './snooze_rule'; import { unsnoozeRuleRoute } from './unsnooze_rule'; import { runSoonRoute } from './run_soon'; import { bulkDeleteRulesRoute } from './bulk_delete_rules'; +import { bulkEnableRulesRoute } from './bulk_enable_rules'; +import { cloneRuleRoute } from './clone_rule'; export interface RouteOptions { router: IRouter; @@ -78,7 +80,9 @@ export function defineRoutes(opts: RouteOptions) { updateRuleApiKeyRoute(router, licenseState); bulkEditInternalRulesRoute(router, licenseState); bulkDeleteRulesRoute({ router, licenseState }); + bulkEnableRulesRoute({ router, licenseState }); snoozeRuleRoute(router, licenseState); unsnoozeRuleRoute(router, licenseState); runSoonRoute(router, licenseState); + cloneRuleRoute(router, licenseState); } diff --git a/x-pack/plugins/alerting/server/routes/lib/actions_schema.ts b/x-pack/plugins/alerting/server/routes/lib/actions_schema.ts new file mode 100644 index 00000000000000..d89873a48c2ea1 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/lib/actions_schema.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { validateDurationSchema } from '../../lib'; + +export const actionsSchema = schema.arrayOf( + schema.object({ + group: schema.string(), + id: schema.string(), + params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + frequency: schema.maybe( + schema.object({ + summary: schema.boolean(), + notify_when: schema.oneOf([ + schema.literal('onActionGroupChange'), + schema.literal('onActiveAlert'), + schema.literal('onThrottleInterval'), + ]), + throttle: schema.nullable(schema.string({ validate: validateDurationSchema })), + }) + ), + }), + { defaultValue: [] } +); diff --git a/x-pack/plugins/alerting/server/routes/lib/index.ts b/x-pack/plugins/alerting/server/routes/lib/index.ts index cda768e7b363b2..387a6f11a5e533 100644 --- a/x-pack/plugins/alerting/server/routes/lib/index.ts +++ b/x-pack/plugins/alerting/server/routes/lib/index.ts @@ -18,5 +18,7 @@ export type { } from './rewrite_request_case'; export { verifyAccessAndContext } from './verify_access_and_context'; export { countUsageOfPredefinedIds } from './count_usage_of_predefined_ids'; +export { rewriteActions } from './rewrite_actions'; +export { actionsSchema } from './actions_schema'; export { rewriteRule, rewriteRuleLastRun } from './rewrite_rule'; export { rewriteNamespaces } from './rewrite_namespaces'; diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts new file mode 100644 index 00000000000000..37b39548c610f1 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_actions.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { CamelToSnake, RewriteRequestCase } from './rewrite_request_case'; +import { RuleAction } from '../../types'; + +type ReqRuleAction = Omit & { + frequency?: { + [K in keyof NonNullable as CamelToSnake]: NonNullable< + RuleAction['frequency'] + >[K]; + }; +}; +export const rewriteActions: ( + actions?: ReqRuleAction[] +) => Array> = (actions) => { + const rewriteFrequency: RewriteRequestCase> = ({ + notify_when: notifyWhen, + ...rest + }) => ({ ...rest, notifyWhen }); + if (!actions) return []; + return actions.map( + (action) => + ({ + ...action, + ...(action.frequency ? { frequency: rewriteFrequency(action.frequency) } : {}), + } as RuleAction) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts index 4eb352d2b2b8cc..5cd64ebf52732c 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_request_case.ts @@ -71,7 +71,7 @@ export type RewriteResponseCase = ( * * For more details see this PR comment: https://github.com/microsoft/TypeScript/pull/40336#issuecomment-686723087 */ -type CamelToSnake = string extends T +export type CamelToSnake = string extends T ? string : T extends `${infer C0}${infer C1}${infer R}` ? `${C0 extends Uppercase ? '_' : ''}${Lowercase}${C1 extends Uppercase diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts index 270db64812b281..47dc3a78c278cc 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts @@ -56,11 +56,19 @@ export const rewriteRule = ({ last_execution_date: executionStatus.lastExecutionDate, last_duration: executionStatus.lastDuration, }, - actions: actions.map(({ group, id, actionTypeId, params }) => ({ + actions: actions.map(({ group, id, actionTypeId, params, frequency }) => ({ group, id, params, connector_type_id: actionTypeId, + ...(frequency + ? { + frequency: { + ...frequency, + notify_when: frequency.notifyWhen, + }, + } + : {}), })), ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), diff --git a/x-pack/plugins/alerting/server/routes/update_rule.ts b/x-pack/plugins/alerting/server/routes/update_rule.ts index 2b7f6b3c98b398..c998d5eb50a518 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.ts @@ -15,6 +15,8 @@ import { RewriteResponseCase, RewriteRequestCase, handleDisabledApiKeysError, + rewriteActions, + actionsSchema, rewriteRuleLastRun, } from './lib'; import { @@ -35,17 +37,10 @@ const bodySchema = schema.object({ schedule: schema.object({ interval: schema.string({ validate: validateDurationSchema }), }), - throttle: schema.nullable(schema.string({ validate: validateDurationSchema })), + throttle: schema.nullable(schema.maybe(schema.string({ validate: validateDurationSchema }))), params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), - actions: schema.arrayOf( - schema.object({ - group: schema.string(), - id: schema.string(), - params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), - }), - { defaultValue: [] } - ), - notify_when: schema.string({ validate: validateNotifyWhenType }), + actions: actionsSchema, + notify_when: schema.maybe(schema.string({ validate: validateNotifyWhenType })), }); const rewriteBodyReq: RewriteRequestCase> = (result) => { @@ -137,6 +132,7 @@ export const updateRuleRoute = ( id, data: { ...rule, + actions: rewriteActions(rule.actions), notify_when: rule.notify_when as RuleNotifyWhenType, }, }) diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index 46a6c36bdea2a5..b3844ddd5f74bb 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -38,11 +38,13 @@ const createRulesClientMock = () => { getSpaceId: jest.fn(), bulkEdit: jest.fn(), bulkDeleteRules: jest.fn(), + bulkEnableRules: jest.fn(), snooze: jest.fn(), unsnooze: jest.fn(), calculateIsSnoozedUntil: jest.fn(), clearExpiredSnoozes: jest.fn(), runSoon: jest.fn(), + clone: jest.fn(), }; return mocked; }; 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 a221327d938ef4..0f91b6793dd90b 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/index.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/index.ts @@ -9,5 +9,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 { retryIfBulkEnableConflicts } from './retry_if_bulk_enable_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_enable_conflicts.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_enable_conflicts.ts new file mode 100644 index 00000000000000..0e5c493348086c --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_enable_conflicts.ts @@ -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 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 { BulkOperationError } from '../rules_client'; +import { waitBeforeNextRetry, RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; + +const MAX_RULES_IDS_IN_RETRY = 1000; + +export type BulkEnableOperation = (filter: KueryNode | null) => Promise<{ + errors: BulkOperationError[]; + taskIdsToEnable: string[]; +}>; + +interface ReturnRetry { + errors: BulkOperationError[]; + taskIdsToEnable: string[]; +} + +/** + * Retries BulkEnable requests + * If in response are presents conflicted savedObjects(409 statusCode), this util constructs filter with failed SO ids and retries bulkEnable operation until + * all SO updated or number of retries exceeded + * @param logger + * @param bulkEnableOperation + * @param filter - KueryNode filter + * @param retries - number of retries left + * @param accErrors - accumulated conflict errors + * @param accTaskIdsToEnable - accumulated task ids + * @returns Promise + */ + +export const retryIfBulkEnableConflicts = async ( + logger: Logger, + bulkEnableOperation: BulkEnableOperation, + filter: KueryNode | null, + retries: number = RETRY_IF_CONFLICTS_ATTEMPTS, + accErrors: BulkOperationError[] = [], + accTaskIdsToEnable: string[] = [] +): Promise => { + try { + const { errors: currentErrors, taskIdsToEnable: currentTaskIdsToEnable } = + await bulkEnableOperation(filter); + + const taskIdsToEnable = [...accTaskIdsToEnable, ...currentTaskIdsToEnable]; + 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 { + errors, + taskIdsToEnable, + }; + } + + if (retries <= 0) { + logger.warn('Bulk enable rules conflicts, exceeded retries'); + + return { + errors, + taskIdsToEnable, + }; + } + + logger.debug( + `Bulk enable 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) => + retryIfBulkEnableConflicts( + logger, + bulkEnableOperation, + convertRuleIdsToKueryNode(queryIds), + retries - 1, + errors, + taskIdsToEnable + ), + { + concurrency: 1, + } + ) + ).reduce( + (acc, item) => { + return { + errors: [...acc.errors, ...item.errors], + taskIdsToEnable: [...acc.taskIdsToEnable, ...item.taskIdsToEnable], + }; + }, + { errors: [], taskIdsToEnable: [] } + ); + } catch (err) { + throw err; + } +}; 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 67a347f5d45c1a..5d113a3b58cc9c 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -111,6 +111,7 @@ import { validateOperationOnAttributes, retryIfBulkEditConflicts, retryIfBulkDeleteConflicts, + retryIfBulkEnableConflicts, applyBulkEditOperation, buildKueryNodeFilter, } from './lib'; @@ -138,6 +139,7 @@ import { RuleMutedError } from '../lib/errors/rule_muted'; import { formatExecutionErrorsResult } from '../lib/format_execution_log_errors'; import { getActiveScheduledSnoozes } from '../lib/is_rule_snoozed'; import { isSnoozeExpired } from '../lib'; +import { isDetectionEngineAADRuleType } from '../saved_objects/migrations/utils'; export interface RegistryAlertTypeWithAuth extends RegistryRuleType { authorizedConsumers: string[]; @@ -309,15 +311,15 @@ export type BulkEditOptions = | BulkEditOptionsFilter | BulkEditOptionsIds; -export interface BulkDeleteOptionsFilter { +interface BulkOptionsFilter { filter?: string | KueryNode; } -export interface BulkDeleteOptionsIds { +interface BulkOptionsIds { ids?: string[]; } -export type BulkDeleteOptions = BulkDeleteOptionsFilter | BulkDeleteOptionsIds; +export type BulkOptions = BulkOptionsFilter | BulkOptionsIds; export interface BulkOperationError { message: string; @@ -359,6 +361,11 @@ export interface FindResult { data: Array>; } +interface SavedObjectOptions { + id?: string; + migrationVersion?: Record; +} + export interface CreateOptions { data: Omit< Rule, @@ -378,10 +385,7 @@ export interface CreateOptions { | 'lastRun' | 'nextRun' > & { actions: NormalizedAlertAction[] }; - options?: { - id?: string; - migrationVersion?: Record; - }; + options?: SavedObjectOptions; } export interface UpdateOptions { @@ -392,8 +396,8 @@ export interface UpdateOptions { schedule: IntervalSchedule; actions: NormalizedAlertAction[]; params: Params; - throttle: string | null; - notifyWhen: RuleNotifyWhenType | null; + throttle?: string | null; + notifyWhen?: RuleNotifyWhenType | null; }; } @@ -456,6 +460,8 @@ interface ScheduleTaskOptions { throwOnConflict: boolean; // whether to throw conflict errors or swallow them } +type BulkAction = 'DELETE' | 'ENABLE'; + // NOTE: Changing this prefix will require a migration to update the prefix in all existing `rule` saved objects const extractedSavedObjectParamReferenceNamePrefix = 'param:'; @@ -538,6 +544,112 @@ export class RulesClient { this.eventLogger = eventLogger; } + public async clone( + id: string, + { newId }: { newId?: string } + ): Promise> { + let ruleSavedObject: SavedObject; + + try { + ruleSavedObject = await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser( + 'alert', + id, + { + namespace: this.namespace, + } + ); + } catch (e) { + // We'll skip invalidating the API key since we failed to load the decrypted saved object + this.logger.error( + `update(): Failed to load API key to invalidate on alert ${id}: ${e.message}` + ); + // Still attempt to load the object using SOC + ruleSavedObject = await this.unsecuredSavedObjectsClient.get('alert', id); + } + + /* + * As the time of the creation of this PR, security solution already have a clone/duplicate API + * with some specific business logic so to avoid weird bugs, I prefer to exclude them from this + * functionality until we resolve our difference + */ + if ( + isDetectionEngineAADRuleType(ruleSavedObject) || + ruleSavedObject.attributes.consumer === AlertConsumers.SIEM + ) { + throw Boom.badRequest( + 'The clone functionality is not enable for rule who belongs to security solution' + ); + } + const ruleName = + ruleSavedObject.attributes.name.indexOf('[Clone]') > 0 + ? ruleSavedObject.attributes.name + : `${ruleSavedObject.attributes.name} [Clone]`; + const ruleId = newId ?? SavedObjectsUtils.generateId(); + try { + await this.authorization.ensureAuthorized({ + ruleTypeId: ruleSavedObject.attributes.alertTypeId, + consumer: ruleSavedObject.attributes.consumer, + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.CREATE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + this.ruleTypeRegistry.ensureRuleTypeEnabled(ruleSavedObject.attributes.alertTypeId); + // Throws an error if alert type isn't registered + const ruleType = this.ruleTypeRegistry.get(ruleSavedObject.attributes.alertTypeId); + const username = await this.getUserName(); + const createTime = Date.now(); + const lastRunTimestamp = new Date(); + const legacyId = Semver.lt(this.kibanaVersion, '8.0.0') ? id : null; + let createdAPIKey = null; + try { + createdAPIKey = ruleSavedObject.attributes.enabled + ? await this.createAPIKey(this.generateAPIKeyName(ruleType.id, ruleName)) + : null; + } catch (error) { + throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`); + } + const rawRule: RawRule = { + ...ruleSavedObject.attributes, + name: ruleName, + ...this.apiKeyAsAlertAttributes(createdAPIKey, username), + legacyId, + createdBy: username, + updatedBy: username, + createdAt: new Date(createTime).toISOString(), + updatedAt: new Date(createTime).toISOString(), + snoozeSchedule: [], + muteAll: false, + mutedInstanceIds: [], + executionStatus: getRuleExecutionStatusPending(lastRunTimestamp.toISOString()), + monitoring: getDefaultMonitoring(lastRunTimestamp.toISOString()), + }; + + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.CREATE, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + return await this.createRuleSavedObject({ + intervalInMs: parseDuration(rawRule.schedule.interval), + rawRule, + references: ruleSavedObject.references, + ruleId, + }); + } + public async create({ data, options, @@ -579,7 +691,7 @@ export class RulesClient { throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`); } - await this.validateActions(ruleType, data.actions); + await this.validateActions(ruleType, data); // Throw error if schedule interval is less than the minimum and we are enforcing it const intervalInMs = parseDuration(data.schedule.interval); @@ -599,7 +711,8 @@ export class RulesClient { const createTime = Date.now(); const lastRunTimestamp = new Date(); const legacyId = Semver.lt(this.kibanaVersion, '8.0.0') ? id : null; - const notifyWhen = getRuleNotifyWhenType(data.notifyWhen, data.throttle); + const notifyWhen = getRuleNotifyWhenType(data.notifyWhen ?? null, data.throttle ?? null); + const throttle = data.throttle ?? null; const rawRule: RawRule = { ...data, @@ -615,6 +728,7 @@ export class RulesClient { muteAll: false, mutedInstanceIds: [], notifyWhen, + throttle, executionStatus: getRuleExecutionStatusPending(lastRunTimestamp.toISOString()), monitoring: getDefaultMonitoring(lastRunTimestamp.toISOString()), }; @@ -625,11 +739,33 @@ export class RulesClient { rawRule.mapped_params = mappedParams; } + return await this.createRuleSavedObject({ + intervalInMs, + rawRule, + references, + ruleId: id, + options, + }); + } + + private async createRuleSavedObject({ + intervalInMs, + rawRule, + references, + ruleId, + options, + }: { + intervalInMs: number; + rawRule: RawRule; + references: SavedObjectReference[]; + ruleId: string; + options?: SavedObjectOptions; + }) { this.auditLogger?.log( ruleAuditEvent({ action: RuleAuditAction.CREATE, outcome: 'unknown', - savedObject: { type: 'alert', id }, + savedObject: { type: 'alert', id: ruleId }, }) ); @@ -641,7 +777,7 @@ export class RulesClient { { ...options, references, - id, + id: ruleId, } ); } catch (e) { @@ -654,14 +790,14 @@ export class RulesClient { throw e; } - if (data.enabled) { + if (rawRule.enabled) { let scheduledTask; try { scheduledTask = await this.scheduleTask({ id: createdAlert.id, - consumer: data.consumer, + consumer: rawRule.consumer, ruleTypeId: rawRule.alertTypeId, - schedule: data.schedule, + schedule: rawRule.schedule, throwOnConflict: true, }); } catch (e) { @@ -685,7 +821,7 @@ export class RulesClient { // Log warning if schedule interval is less than the minimum but we're not enforcing it if (intervalInMs < this.minimumScheduleIntervalInMs && !this.minimumScheduleInterval.enforce) { this.logger.warn( - `Rule schedule interval (${data.schedule.interval}) for "${createdAlert.attributes.alertTypeId}" rule type with ID "${createdAlert.id}" is less than the minimum value (${this.minimumScheduleInterval.value}). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent creation of these rules.` + `Rule schedule interval (${rawRule.schedule.interval}) for "${createdAlert.attributes.alertTypeId}" rule type with ID "${createdAlert.id}" is less than the minimum value (${this.minimumScheduleInterval.value}). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent creation of these rules.` ); } @@ -1457,7 +1593,7 @@ export class RulesClient { terms: { field: 'alert.attributes.muteAll' }, }, tags: { - terms: { field: 'alert.attributes.tags', order: { _key: 'asc' } }, + terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, }, snoozed: { nested: { @@ -1736,7 +1872,7 @@ export class RulesClient { // Validate const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate?.params); - await this.validateActions(ruleType, data.actions); + await this.validateActions(ruleType, data); // Throw error if schedule interval is less than the minimum and we are enforcing it const intervalInMs = parseDuration(data.schedule.interval); @@ -1765,7 +1901,7 @@ export class RulesClient { } const apiKeyAttributes = this.apiKeyAsAlertAttributes(createdAPIKey, username); - const notifyWhen = getRuleNotifyWhenType(data.notifyWhen, data.throttle); + const notifyWhen = getRuleNotifyWhenType(data.notifyWhen ?? null, data.throttle ?? null); let updatedObject: SavedObject; const createAttributes = this.updateMeta({ @@ -1824,7 +1960,7 @@ export class RulesClient { ); } - private getAuthorizationFilter = async () => { + private getAuthorizationFilter = async ({ action }: { action: BulkAction }) => { try { const authorizationTuple = await this.authorization.getFindAuthorizationFilter( AlertingAuthorizationEntity.Rule, @@ -1834,7 +1970,7 @@ export class RulesClient { } catch (error) { this.auditLogger?.log( ruleAuditEvent({ - action: RuleAuditAction.DELETE, + action: RuleAuditAction[action], error, }) ); @@ -1842,9 +1978,9 @@ export class RulesClient { } }; - public bulkDeleteRules = async (options: BulkDeleteOptions) => { - const filter = (options as BulkDeleteOptionsFilter).filter; - const ids = (options as BulkDeleteOptionsIds).ids; + private getAndValidateCommonBulkOptions = (options: BulkOptions) => { + const filter = (options as BulkOptionsFilter).filter; + const ids = (options as BulkOptionsIds).ids; if (!ids && !filter) { throw Boom.badRequest( @@ -1861,20 +1997,34 @@ export class RulesClient { "Both 'filter' and 'ids' are supplied. Define either 'ids' or 'filter' properties in method's arguments" ); } + return { ids, filter }; + }; - const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); - const authorizationFilter = await this.getAuthorizationFilter(); - - const kueryNodeFilterWithAuth = - authorizationFilter && kueryNodeFilter - ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) - : kueryNodeFilter; - + private checkAuthorizationAndGetTotal = async ({ + filter, + action, + }: { + filter: KueryNode | null; + action: BulkAction; + }) => { + const actionToConstantsMapping: Record< + BulkAction, + { WriteOperation: WriteOperations | ReadOperations; RuleAuditAction: RuleAuditAction } + > = { + DELETE: { + WriteOperation: WriteOperations.BulkDelete, + RuleAuditAction: RuleAuditAction.DELETE, + }, + ENABLE: { + WriteOperation: WriteOperations.BulkEnable, + RuleAuditAction: RuleAuditAction.ENABLE, + }, + }; const { aggregations, total } = await this.unsecuredSavedObjectsClient.find< RawRule, RuleBulkOperationAggregation >({ - filter: kueryNodeFilterWithAuth, + filter, page: 1, perPage: 0, type: 'alert', @@ -1892,31 +2042,31 @@ export class RulesClient { if (total > MAX_RULES_NUMBER_FOR_BULK_OPERATION) { throw Boom.badRequest( - `More than ${MAX_RULES_NUMBER_FOR_BULK_OPERATION} rules matched for bulk delete` + `More than ${MAX_RULES_NUMBER_FOR_BULK_OPERATION} rules matched for bulk ${action.toLocaleLowerCase()}` ); } const buckets = aggregations?.alertTypeId.buckets; if (buckets === undefined || buckets?.length === 0) { - throw Boom.badRequest('No rules found for bulk delete'); + throw Boom.badRequest(`No rules found for bulk ${action.toLocaleLowerCase()}`); } await pMap( buckets, - async ({ key: [ruleType, consumer] }) => { + async ({ key: [ruleType, consumer, actions] }) => { this.ruleTypeRegistry.ensureRuleTypeEnabled(ruleType); try { await this.authorization.ensureAuthorized({ ruleTypeId: ruleType, consumer, - operation: WriteOperations.BulkDelete, + operation: actionToConstantsMapping[action].WriteOperation, entity: AlertingAuthorizationEntity.Rule, }); } catch (error) { this.auditLogger?.log( ruleAuditEvent({ - action: RuleAuditAction.DELETE, + action: actionToConstantsMapping[action].RuleAuditAction, error, }) ); @@ -1925,6 +2075,24 @@ export class RulesClient { }, { concurrency: RULE_TYPE_CHECKS_CONCURRENCY } ); + return { total }; + }; + + public bulkDeleteRules = async (options: BulkOptions) => { + const { ids, filter } = this.getAndValidateCommonBulkOptions(options); + + const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); + const authorizationFilter = await this.getAuthorizationFilter({ action: 'DELETE' }); + + const kueryNodeFilterWithAuth = + authorizationFilter && kueryNodeFilter + ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) + : kueryNodeFilter; + + const { total } = await this.checkAuthorizationAndGetTotal({ + filter: kueryNodeFilterWithAuth, + action: 'DELETE', + }); const { apiKeysToInvalidate, errors, taskIdsToDelete } = await retryIfBulkDeleteConflicts( this.logger, @@ -2263,7 +2431,7 @@ export class RulesClient { for (const operation of operations) { switch (operation.field) { case 'actions': - await this.validateActions(ruleType, operation.value); + await this.validateActions(ruleType, { ...attributes, actions: operation.value }); ruleActions = applyBulkEditOperation(operation, ruleActions); break; case 'snoozeSchedule': @@ -2373,7 +2541,7 @@ export class RulesClient { // get notifyWhen const notifyWhen = getRuleNotifyWhenType( - attributes.notifyWhen, + attributes.notifyWhen ?? null, attributes.throttle ?? null ); @@ -2457,6 +2625,190 @@ export class RulesClient { return { apiKeysToInvalidate, resultSavedObjects: result.saved_objects, errors, rules }; } + private getShouldScheduleTask = async (scheduledTaskId: string | null | undefined) => { + if (!scheduledTaskId) return true; + try { + // make sure scheduledTaskId exist + await this.taskManager.get(scheduledTaskId); + return false; + } catch (err) { + return true; + } + }; + + public bulkEnableRules = async (options: BulkOptions) => { + const { ids, filter } = this.getAndValidateCommonBulkOptions(options); + + const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); + const authorizationFilter = await this.getAuthorizationFilter({ action: 'ENABLE' }); + + const kueryNodeFilterWithAuth = + authorizationFilter && kueryNodeFilter + ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) + : kueryNodeFilter; + + const { total } = await this.checkAuthorizationAndGetTotal({ + filter: kueryNodeFilterWithAuth, + action: 'ENABLE', + }); + + const { errors, taskIdsToEnable } = await retryIfBulkEnableConflicts( + this.logger, + (filterKueryNode: KueryNode | null) => + this.bulkEnableRulesWithOCC({ filter: filterKueryNode }), + kueryNodeFilterWithAuth + ); + + const taskIdsFailedToBeEnabled: string[] = []; + if (taskIdsToEnable.length > 0) { + try { + const resultFromEnablingTasks = await this.taskManager.bulkEnable(taskIdsToEnable); + resultFromEnablingTasks?.errors?.forEach((error) => { + taskIdsFailedToBeEnabled.push(error.task.id); + }); + this.logger.debug( + `Successfully enabled schedules for underlying tasks: ${taskIdsToEnable + .filter((id) => !taskIdsFailedToBeEnabled.includes(id)) + .join(', ')}` + ); + } catch (error) { + taskIdsFailedToBeEnabled.push(...taskIdsToEnable); + this.logger.error( + `Failure to enable schedules for underlying tasks: ${taskIdsToEnable.join( + ', ' + )}. TaskManager bulkEnable failed with Error: ${error.message}` + ); + } + } + + return { errors, total, taskIdsFailedToBeEnabled }; + }; + + private bulkEnableRulesWithOCC = async ({ filter }: { filter: KueryNode | null }) => { + const rulesFinder = + await this.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( + { + filter, + type: 'alert', + perPage: 100, + ...(this.namespace ? { namespaces: [this.namespace] } : undefined), + } + ); + + const rulesToEnable: SavedObjectsBulkUpdateObject[] = []; + const taskIdsToEnable: string[] = []; + const errors: BulkOperationError[] = []; + const taskIdToRuleIdMapping: Record = {}; + const ruleNameToRuleIdMapping: Record = {}; + + for await (const response of rulesFinder.find()) { + await pMap(response.saved_objects, async (rule) => { + try { + if (rule.attributes.actions.length) { + try { + await this.actionsAuthorization.ensureAuthorized('execute'); + } catch (error) { + throw Error(`Rule not authorized for bulk enable - ${error.message}`); + } + } + if (rule.attributes.enabled === true) return; + if (rule.attributes.name) { + ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; + } + if (rule.attributes.scheduledTaskId) { + taskIdToRuleIdMapping[rule.id] = rule.attributes.scheduledTaskId; + } + + const username = await this.getUserName(); + + const updatedAttributes = this.updateMeta({ + ...rule.attributes, + ...(!rule.attributes.apiKey && + (await this.createNewAPIKeySet({ attributes: rule.attributes, username }))), + enabled: true, + updatedBy: username, + updatedAt: new Date().toISOString(), + executionStatus: { + status: 'pending', + lastDuration: 0, + lastExecutionDate: new Date().toISOString(), + error: null, + warning: null, + }, + }); + + const shouldScheduleTask = await this.getShouldScheduleTask( + rule.attributes.scheduledTaskId + ); + + let scheduledTaskId; + if (shouldScheduleTask) { + const scheduledTask = await this.scheduleTask({ + id: rule.id, + consumer: rule.attributes.consumer, + ruleTypeId: rule.attributes.alertTypeId, + schedule: rule.attributes.schedule as IntervalSchedule, + throwOnConflict: false, + }); + scheduledTaskId = scheduledTask.id; + } + + rulesToEnable.push({ + ...rule, + attributes: { + ...updatedAttributes, + ...(scheduledTaskId ? { scheduledTaskId } : undefined), + }, + }); + + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.ENABLE, + outcome: 'unknown', + savedObject: { type: 'alert', id: rule.id }, + }) + ); + } catch (error) { + errors.push({ + message: error.message, + rule: { + id: rule.id, + name: rule.attributes?.name, + }, + }); + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.ENABLE, + error, + }) + ); + } + }); + } + + const result = await this.unsecuredSavedObjectsClient.bulkCreate(rulesToEnable, { + overwrite: true, + }); + + result.saved_objects.forEach((rule) => { + if (rule.error === undefined) { + if (taskIdToRuleIdMapping[rule.id]) { + taskIdsToEnable.push(taskIdToRuleIdMapping[rule.id]); + } + } else { + errors.push({ + message: rule.error.message ?? 'n/a', + status: rule.error.statusCode, + rule: { + id: rule.id, + name: ruleNameToRuleIdMapping[rule.id] ?? 'n/a', + }, + }); + } + }); + return { errors, taskIdsToEnable }; + }; + private apiKeyAsAlertAttributes( apiKey: CreateAPIKeyResult | null, username: string | null @@ -3554,8 +3906,23 @@ export class RulesClient { private async validateActions( alertType: UntypedNormalizedRuleType, - actions: NormalizedAlertAction[] + data: Pick & { actions: NormalizedAlertAction[] } ): Promise { + const { actions, notifyWhen, throttle } = data; + const hasNotifyWhen = typeof notifyWhen !== 'undefined'; + const hasThrottle = typeof throttle !== 'undefined'; + let usesRuleLevelFreqParams; + if (hasNotifyWhen && hasThrottle) usesRuleLevelFreqParams = true; + else if (!hasNotifyWhen && !hasThrottle) usesRuleLevelFreqParams = false; + else { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.usesValidGlobalFreqParams.oneUndefined', { + defaultMessage: + 'Rule-level notifyWhen and throttle must both be defined or both be undefined', + }) + ); + } + if (actions.length === 0) { return; } @@ -3598,6 +3965,34 @@ export class RulesClient { }) ); } + + // check for actions using frequency params if the rule has rule-level frequency params defined + if (usesRuleLevelFreqParams) { + const actionsWithFrequency = actions.filter((action) => Boolean(action.frequency)); + if (actionsWithFrequency.length) { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.validateActions.mixAndMatchFreqParams', { + defaultMessage: + 'Cannot specify per-action frequency params when notify_when and throttle are defined at the rule level: {groups}', + values: { + groups: actionsWithFrequency.map((a) => a.group).join(', '), + }, + }) + ); + } + } else { + const actionsWithoutFrequency = actions.filter((action) => !action.frequency); + if (actionsWithoutFrequency.length) { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.validateActions.notAllActionsWithFreq', { + defaultMessage: 'Actions missing frequency parameters: {groups}', + values: { + groups: actionsWithoutFrequency.map((a) => a.group).join(', '), + }, + }) + ); + } + } } private async extractReferences< diff --git a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts index 494af8f668bfb0..937c027ed04ce5 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts @@ -227,7 +227,7 @@ describe('aggregate()', () => { }, }, tags: { - terms: { field: 'alert.attributes.tags', order: { _key: 'asc' } }, + terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, }, }, }, @@ -285,7 +285,7 @@ describe('aggregate()', () => { }, }, tags: { - terms: { field: 'alert.attributes.tags', order: { _key: 'asc' } }, + terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, }, }, }, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts new file mode 100644 index 00000000000000..bad45b209ab150 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts @@ -0,0 +1,624 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 type { SavedObject } from '@kbn/core-saved-objects-common'; +import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; +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 { loggerMock } from '@kbn/logging-mocks'; +import { BulkUpdateTaskResult } from '@kbn/task-manager-plugin/server/task_scheduling'; + +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('bulkEnableRules', () => { + let rulesClient: RulesClient; + const defaultRule = { + id: 'id1', + type: 'alert', + attributes: { + name: 'fakeName', + consumer: 'fakeConsumer', + alertTypeId: 'fakeType', + schedule: { interval: '5m' }, + actions: [ + { + group: 'default', + actionTypeId: '1', + actionRef: '1', + params: { + foo: true, + }, + }, + ], + }, + references: [], + version: '1', + }; + const existingDecryptedRule1 = { + ...defaultRule, + attributes: { + ...defaultRule.attributes, + enabled: false, + scheduledTaskId: 'taskId1', + apiKey: Buffer.from('123:abc').toString('base64'), + }, + }; + const existingDecryptedRule2 = { + ...defaultRule, + id: 'id2', + attributes: { + ...defaultRule.attributes, + enabled: false, + 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; + }, + }); + }; + + const mockUnsecuredSavedObjectFind = (total: number) => { + unsecuredSavedObjectsClient.find.mockResolvedValue({ + aggregations: { + alertTypeId: { + buckets: [ + { + key: ['fakeType', 'fakeConsumer'], + key_as_string: 'fakeType|fakeConsumer', + doc_count: total, + }, + ], + }, + }, + saved_objects: [], + per_page: 0, + page: 0, + total, + }); + }; + + beforeEach(async () => { + rulesClient = new RulesClient(rulesClientParams); + authorization.getFindAuthorizationFilter.mockResolvedValue({ + ensureRuleTypeIsAuthorized() {}, + }); + mockCreatePointInTimeFinderAsInternalUser(); + mockUnsecuredSavedObjectFind(2); + }); + + test('should enable two rule', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + id: 'id1', + version: '1', + } as SavedObject, + { + id: 'id2', + version: '1', + } as SavedObject, + ], + }); + + const result = await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); + + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + id: 'id1', + attributes: expect.objectContaining({ + enabled: true, + }), + }), + expect.objectContaining({ + id: 'id2', + attributes: expect.objectContaining({ + enabled: true, + }), + }), + ]), + { overwrite: true } + ); + + expect(taskManager.bulkEnable).toHaveBeenCalledTimes(1); + expect(taskManager.bulkEnable).toHaveBeenCalledWith(['taskId1', 'taskId2']); + expect(result).toStrictEqual({ + errors: [], + total: 2, + taskIdsFailedToBeEnabled: [], + }); + }); + + test('should try to enable rules, one successful and one with 500 error', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + id: 'id1', + version: '1', + } as SavedObject, + { + id: 'id2', + error: { + error: '', + message: 'UPS', + statusCode: 500, + }, + version: '1', + } as SavedObject, + ], + }); + + const result = await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); + + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + id: 'id1', + attributes: expect.objectContaining({ + enabled: true, + }), + }), + ]), + { overwrite: true } + ); + + expect(taskManager.bulkEnable).toHaveBeenCalledTimes(1); + expect(taskManager.bulkEnable).toHaveBeenCalledWith(['taskId1']); + expect(result).toStrictEqual({ + errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 500 }], + total: 2, + taskIdsFailedToBeEnabled: [], + }); + }); + + test('should try to enable rules, one successful and one with 409 error, which will not be deleted with retry', async () => { + unsecuredSavedObjectsClient.bulkCreate + .mockResolvedValueOnce({ + saved_objects: [ + { + id: 'id1', + version: '1', + } as SavedObject, + { + id: 'id2', + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + version: '1', + } as SavedObject, + ], + }) + .mockResolvedValueOnce({ + saved_objects: [ + { + id: 'id2', + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + version: '1', + } as SavedObject, + ], + }) + .mockResolvedValueOnce({ + saved_objects: [ + { + id: 'id2', + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + version: '1', + } as SavedObject, + ], + }); + + 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.bulkEnableRules({ ids: ['id1', 'id2'] }); + + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(3); + expect(taskManager.bulkEnable).toHaveBeenCalledTimes(1); + expect(taskManager.bulkEnable).toHaveBeenCalledWith(['taskId1']); + expect(result).toStrictEqual({ + errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 409 }], + total: 2, + taskIdsFailedToBeEnabled: [], + }); + }); + + test('should try to enable rules, one successful and one with 409 error, which successfully will be deleted with retry', async () => { + unsecuredSavedObjectsClient.bulkCreate + .mockResolvedValueOnce({ + saved_objects: [ + { + id: 'id1', + version: '1', + } as SavedObject, + { + id: 'id2', + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + version: '1', + } as SavedObject, + ], + }) + .mockResolvedValueOnce({ + saved_objects: [ + { + id: 'id2', + version: '1', + } as SavedObject, + ], + }); + + 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.bulkEnableRules({ ids: ['id1', 'id2'] }); + + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(2); + expect(taskManager.bulkEnable).toHaveBeenCalledTimes(1); + expect(taskManager.bulkEnable).toHaveBeenCalledWith(['taskId1', 'taskId2']); + expect(result).toStrictEqual({ + errors: [], + total: 2, + taskIdsFailedToBeEnabled: [], + }); + }); + + 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.bulkEnableRules({ filter: 'fake_filter' })).rejects.toThrow( + 'More than 10000 rules matched for bulk enable' + ); + }); + + test('should throw an error if we do not get buckets', async () => { + unsecuredSavedObjectsClient.find.mockResolvedValue({ + aggregations: { + alertTypeId: {}, + }, + saved_objects: [], + per_page: 0, + page: 0, + total: 2, + }); + + await expect(rulesClient.bulkEnableRules({ filter: 'fake_filter' })).rejects.toThrow( + 'No rules found for bulk enable' + ); + }); + + test('should thow if there are actions, but do not have execute permissions', async () => { + actionsAuthorization.ensureAuthorized.mockImplementation(() => { + throw new Error('UPS'); + }); + + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + id: 'id1', + version: '1', + } as SavedObject, + { + id: 'id2', + version: '1', + } as SavedObject, + ], + }); + + const result = await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); + + expect(result).toStrictEqual({ + errors: [ + { + message: 'Rule not authorized for bulk enable - UPS', + rule: { id: 'id1', name: 'fakeName' }, + }, + { + message: 'Rule not authorized for bulk enable - UPS', + rule: { id: 'id2', name: 'fakeName' }, + }, + ], + taskIdsFailedToBeEnabled: [], + total: 2, + }); + }); + + test('should if rule is already enabled', async () => { + mockCreatePointInTimeFinderAsInternalUser({ + saved_objects: [ + existingDecryptedRule1, + { + ...existingDecryptedRule2, + attributes: { ...existingDecryptedRule2.attributes, enabled: true }, + }, + ], + }); + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + id: 'id1', + version: '1', + } as SavedObject, + { + id: 'id2', + version: '1', + } as SavedObject, + ], + }); + + const result = await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); + + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + id: 'id1', + attributes: expect.objectContaining({ + enabled: true, + }), + }), + ]), + { overwrite: true } + ); + + expect(taskManager.bulkEnable).toHaveBeenCalledTimes(1); + expect(taskManager.bulkEnable).toHaveBeenCalledWith(['taskId1']); + expect(result).toStrictEqual({ + errors: [], + total: 2, + taskIdsFailedToBeEnabled: [], + }); + }); + + describe('taskManager', () => { + test('should return task id if deleting task failed', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + id: 'id1', + version: '1', + } as SavedObject, + { + id: 'id2', + version: '1', + } as SavedObject, + ], + }); + taskManager.bulkEnable.mockImplementation( + async () => + ({ + tasks: [{ id: 'taskId1' }, { id: 'taskId2' }], + errors: [ + { + task: { + id: 'taskId2', + }, + error: { + error: '', + message: 'UPS', + statusCode: 500, + }, + }, + ], + } as unknown as BulkUpdateTaskResult) + ); + + const result = await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); + + expect(logger.debug).toBeCalledTimes(1); + expect(logger.debug).toBeCalledWith( + 'Successfully enabled schedules for underlying tasks: taskId1' + ); + expect(result).toStrictEqual({ + errors: [], + total: 2, + taskIdsFailedToBeEnabled: ['taskId2'], + }); + }); + + test('should not throw an error if taskManager throw an error', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + id: 'id1', + version: '1', + } as SavedObject, + { + id: 'id2', + version: '1', + } as SavedObject, + ], + }); + taskManager.bulkEnable.mockImplementation(() => { + throw new Error('UPS'); + }); + + const result = await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); + + expect(logger.error).toBeCalledTimes(1); + expect(logger.error).toBeCalledWith( + 'Failure to enable schedules for underlying tasks: taskId1, taskId2. TaskManager bulkEnable failed with Error: UPS' + ); + expect(result).toStrictEqual({ + errors: [], + taskIdsFailedToBeEnabled: ['taskId1', 'taskId2'], + total: 2, + }); + }); + }); + + describe('auditLogger', () => { + jest.spyOn(auditLogger, 'log').mockImplementation(); + + test('logs audit event when enabling rules', async () => { + unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ + saved_objects: [ + { + id: 'id1', + version: '1', + } as SavedObject, + ], + }); + + await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); + + expect(auditLogger.log.mock.calls[0][0]?.event?.action).toEqual('rule_enable'); + 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_enable'); + 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 () => { + authorization.ensureAuthorized.mockImplementation(() => { + throw new Error('Unauthorized'); + }); + + await expect(rulesClient.bulkEnableRules({ filter: 'fake_filter' })).rejects.toThrowError( + 'Unauthorized' + ); + + expect(auditLogger.log.mock.calls[0][0]?.event?.action).toEqual('rule_enable'); + expect(auditLogger.log.mock.calls[0][0]?.event?.outcome).toEqual('failure'); + }); + + test('logs audit event when getting an authorization filter failed', async () => { + authorization.getFindAuthorizationFilter.mockImplementation(() => { + throw new Error('Error'); + }); + + await expect(rulesClient.bulkEnableRules({ filter: 'fake_filter' })).rejects.toThrowError( + 'Error' + ); + + expect(auditLogger.log.mock.calls[0][0]?.event?.action).toEqual('rule_enable'); + expect(auditLogger.log.mock.calls[0][0]?.event?.outcome).toEqual('failure'); + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index aad483f09fe9ec..4d9b84e53a2f4f 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -2661,4 +2661,184 @@ describe('create()', () => { expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); }); + + test('throws error when mixing and matching global and per-action frequency values', async () => { + rulesClient = new RulesClient({ + ...rulesClientParams, + minimumScheduleInterval: { value: '1m', enforce: true }, + }); + ruleTypeRegistry.get.mockImplementation(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: jest.fn(), + injectReferences: jest.fn(), + }, + })); + + const data = getMockData({ + notifyWhen: 'onActionGroupChange', + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onActionGroupChange', + throttle: null, + }, + }, + { + group: 'default', + id: '2', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onActionGroupChange', + throttle: null, + }, + }, + ], + }); + await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Cannot specify per-action frequency params when notify_when and throttle are defined at the rule level: default, default"` + ); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + + const data2 = getMockData({ + notifyWhen: null, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onActionGroupChange', + throttle: null, + }, + }, + { + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + ], + }); + await expect(rulesClient.create({ data: data2 })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Cannot specify per-action frequency params when notify_when and throttle are defined at the rule level: default"` + ); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); + + test('throws error when neither global frequency nor action frequency are defined', async () => { + rulesClient = new RulesClient({ + ...rulesClientParams, + minimumScheduleInterval: { value: '1m', enforce: true }, + }); + ruleTypeRegistry.get.mockImplementation(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: jest.fn(), + injectReferences: jest.fn(), + }, + })); + + const data = getMockData({ + notifyWhen: undefined, + throttle: undefined, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + }); + await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Actions missing frequency parameters: default"` + ); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); + test('throws error when some actions are missing frequency params', async () => { + rulesClient = new RulesClient({ + ...rulesClientParams, + minimumScheduleInterval: { value: '1m', enforce: true }, + }); + ruleTypeRegistry.get.mockImplementation(() => ({ + id: '123', + name: 'Test', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() {}, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: jest.fn(), + injectReferences: jest.fn(), + }, + })); + + const data = getMockData({ + notifyWhen: undefined, + throttle: undefined, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onActionGroupChange', + throttle: null, + }, + }, + { + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + ], + }); + await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( + `"Actions missing frequency parameters: default"` + ); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index 9071099ed3aaf3..ae566c107862aa 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -1652,6 +1652,183 @@ describe('update()', () => { expect(taskManager.bulkUpdateSchedules).not.toHaveBeenCalled(); }); + test('throws error when mixing and matching global and per-action frequency values', async () => { + const alertId = uuid.v4(); + const taskId = uuid.v4(); + + mockApiCalls(alertId, taskId, { interval: '1m' }, { interval: '1m' }); + await expect( + rulesClient.update({ + id: alertId, + data: { + schedule: { interval: '1m' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + throttle: null, + notifyWhen: 'onActionGroupChange', + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onActionGroupChange', + throttle: null, + }, + }, + { + group: 'default', + id: '2', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onActionGroupChange', + throttle: null, + }, + }, + ], + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Cannot specify per-action frequency params when notify_when and throttle are defined at the rule level: default, default"` + ); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + + await expect( + rulesClient.update({ + id: alertId, + data: { + schedule: { interval: '1m' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + throttle: null, + notifyWhen: null, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onActionGroupChange', + throttle: null, + }, + }, + { + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + ], + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Cannot specify per-action frequency params when notify_when and throttle are defined at the rule level: default"` + ); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); + + test('throws error when neither global frequency nor action frequency are defined', async () => { + const alertId = uuid.v4(); + const taskId = uuid.v4(); + + mockApiCalls(alertId, taskId, { interval: '1m' }, { interval: '1m' }); + + await expect( + rulesClient.update({ + id: alertId, + data: { + schedule: { interval: '1m' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + notifyWhen: undefined, + throttle: undefined, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Actions missing frequency parameters: default"` + ); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); + + test('throws error when when some actions are missing frequency params', async () => { + const alertId = uuid.v4(); + const taskId = uuid.v4(); + + mockApiCalls(alertId, taskId, { interval: '1m' }, { interval: '1m' }); + + await expect( + rulesClient.update({ + id: alertId, + data: { + schedule: { interval: '1m' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + notifyWhen: undefined, + throttle: undefined, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onActionGroupChange', + throttle: null, + }, + }, + { + group: 'default', + id: '2', + params: { + foo: true, + }, + }, + ], + }, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Actions missing frequency parameters: default"` + ); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); + test('logs when update of schedule of an alerts underlying task fails', async () => { const alertId = uuid.v4(); const taskId = uuid.v4(); diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts index c75107c52cb81e..7a01338108e7c8 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts @@ -13,7 +13,7 @@ import { renderActionParameterTemplatesDefault, } from '@kbn/actions-plugin/server/mocks'; import { KibanaRequest } from '@kbn/core/server'; -import { InjectActionParamsOpts } from './inject_action_params'; +import { InjectActionParamsOpts, injectActionParams } from './inject_action_params'; import { NormalizedRuleType } from '../rule_type_registry'; import { ActionsCompletion, RuleTypeParams, RuleTypeState, SanitizedRule } from '../types'; import { RuleRunMetricsStore } from '../lib/rule_run_metrics_store'; @@ -29,6 +29,8 @@ jest.mock('./inject_action_params', () => ({ injectActionParams: jest.fn(), })); +const injectActionParamsMock = injectActionParams as jest.Mock; + const alertingEventLogger = alertingEventLoggerMock.create(); const actionsClient = actionsClientMock.create(); const mockActionsPlugin = actionsMock.createStart(); @@ -184,39 +186,39 @@ describe('Execution Handler', () => { expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", - }, - ], - ] - `); + Array [ + Array [ + Object { + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ + Object { + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", + }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); expect(alertingEventLogger.logAction).toHaveBeenCalledTimes(1); expect(alertingEventLogger.logAction).toHaveBeenNthCalledWith(1, { @@ -308,7 +310,7 @@ describe('Execution Handler', () => { ]); }); - test('trow error error message when action type is disabled', async () => { + test('throw error message when action type is disabled', async () => { mockActionsPlugin.preconfiguredActions = []; mockActionsPlugin.isActionExecutable.mockReturnValue(false); mockActionsPlugin.isActionTypeEnabled.mockReturnValue(false); @@ -370,39 +372,39 @@ describe('Execution Handler', () => { expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", - "contextVal": "My context-val goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", - }, - ], - ] - `); + Array [ + Array [ + Object { + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", + "contextVal": "My context-val goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ + Object { + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", + }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); }); test('state attribute gets parameterized', async () => { @@ -410,39 +412,39 @@ describe('Execution Handler', () => { await executionHandler.run(generateAlert({ id: 2, state: { value: 'state-val' } })); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My state-val goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", - }, - ], - ] - `); + Array [ + Array [ + Object { + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My state-val goes here", + }, + "relatedSavedObjects": Array [ + Object { + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", + }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); }); test(`logs an error when action group isn't part of actionGroups available for the ruleType`, async () => { @@ -622,39 +624,39 @@ describe('Execution Handler', () => { await executionHandler.run(generateAlert({ id: 1, scheduleActions: false }), true); expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", - }, - ], - ] - `); + Array [ + Array [ + Object { + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ + Object { + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", + }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); }); test('does not schedule alerts with recovered actions that are muted', async () => { @@ -729,4 +731,191 @@ describe('Execution Handler', () => { `skipping scheduling of actions for '1' in rule ${defaultExecutionParams.ruleLabel}: rule is muted` ); }); + + describe('rule url', () => { + const ruleWithUrl = { + ...rule, + actions: [ + { + id: '1', + group: 'default', + actionTypeId: 'test', + params: { + val: 'rule url: {{rule.url}}', + }, + }, + ], + } as unknown as SanitizedRule; + + it('populates the rule.url in the action params when the base url and rule id are specified', async () => { + const execParams = { + ...defaultExecutionParams, + rule: ruleWithUrl, + taskRunnerContext: { + ...defaultExecutionParams.taskRunnerContext, + kibanaBaseUrl: 'http://localhost:12345', + }, + }; + + const executionHandler = new ExecutionHandler(generateExecutionParams(execParams)); + await executionHandler.run(generateAlert({ id: 1 })); + + expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "actionParams": Object { + "val": "rule url: http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1", + }, + "actionTypeId": "test", + "ruleId": "1", + "spaceId": "test1", + }, + ] + `); + }); + + it('populates the rule.url without the space specifier when the spaceId is the string "default"', async () => { + const execParams = { + ...defaultExecutionParams, + rule: ruleWithUrl, + taskRunnerContext: { + ...defaultExecutionParams.taskRunnerContext, + kibanaBaseUrl: 'http://localhost:12345', + }, + taskInstance: { + params: { spaceId: 'default', alertId: '1' }, + } as unknown as ConcreteTaskInstance, + }; + + const executionHandler = new ExecutionHandler(generateExecutionParams(execParams)); + await executionHandler.run(generateAlert({ id: 1 })); + + expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "actionParams": Object { + "val": "rule url: http://localhost:12345/app/management/insightsAndAlerting/triggersActions/rule/1", + }, + "actionTypeId": "test", + "ruleId": "1", + "spaceId": "default", + }, + ] + `); + }); + + it('populates the rule.url in the action params when the base url has a trailing slash and removes the trailing slash', async () => { + const execParams = { + ...defaultExecutionParams, + rule: ruleWithUrl, + taskRunnerContext: { + ...defaultExecutionParams.taskRunnerContext, + kibanaBaseUrl: 'http://localhost:12345/', + }, + }; + + const executionHandler = new ExecutionHandler(generateExecutionParams(execParams)); + await executionHandler.run(generateAlert({ id: 1 })); + + expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "actionParams": Object { + "val": "rule url: http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1", + }, + "actionTypeId": "test", + "ruleId": "1", + "spaceId": "test1", + }, + ] + `); + }); + + it('does not populate the rule.url when the base url is not specified', async () => { + const execParams = { + ...defaultExecutionParams, + rule: ruleWithUrl, + taskRunnerContext: { + ...defaultExecutionParams.taskRunnerContext, + kibanaBaseUrl: undefined, + }, + }; + + const executionHandler = new ExecutionHandler(generateExecutionParams(execParams)); + await executionHandler.run(generateAlert({ id: 1 })); + + expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "actionParams": Object { + "val": "rule url: ", + }, + "actionTypeId": "test", + "ruleId": "1", + "spaceId": "test1", + }, + ] + `); + }); + + it('does not populate the rule.url when base url is not a valid url', async () => { + const execParams = { + ...defaultExecutionParams, + rule: ruleWithUrl, + taskRunnerContext: { + ...defaultExecutionParams.taskRunnerContext, + kibanaBaseUrl: 'localhost12345', + }, + taskInstance: { + params: { spaceId: 'test1', alertId: '1' }, + } as unknown as ConcreteTaskInstance, + }; + + const executionHandler = new ExecutionHandler(generateExecutionParams(execParams)); + await executionHandler.run(generateAlert({ id: 1 })); + + expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "actionParams": Object { + "val": "rule url: ", + }, + "actionTypeId": "test", + "ruleId": "1", + "spaceId": "test1", + }, + ] + `); + }); + + it('does not populate the rule.url when base url is a number', async () => { + const execParams = { + ...defaultExecutionParams, + rule: ruleWithUrl, + taskRunnerContext: { + ...defaultExecutionParams.taskRunnerContext, + kibanaBaseUrl: 1, + }, + taskInstance: { + params: { spaceId: 'test1', alertId: '1' }, + } as unknown as ConcreteTaskInstance, + }; + + const executionHandler = new ExecutionHandler(generateExecutionParams(execParams)); + await executionHandler.run(generateAlert({ id: 1 })); + + expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "actionParams": Object { + "val": "rule url: ", + }, + "actionTypeId": "test", + "ruleId": "1", + "spaceId": "test1", + }, + ] + `); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index 2d72bcd284a66a..7bb67253343332 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -7,6 +7,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { Logger } from '@kbn/core/server'; +import { getRuleDetailsRoute, triggersActionsRoute } from '@kbn/rule-data-utils'; import { asSavedObjectExecutionSource } from '@kbn/actions-plugin/server'; import { isEphemeralTaskRejectedDueToCapacityError } from '@kbn/task-manager-plugin/server'; import { ExecuteOptions as EnqueueExecutionOptions } from '@kbn/actions-plugin/server/create_execute_function'; @@ -208,6 +209,7 @@ export class ExecutionHandler< kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl, alertParams: this.rule.params, actionParams: action.params, + ruleUrl: this.buildRuleUrl(spaceId), }), }), }; @@ -282,6 +284,28 @@ export class ExecutionHandler< return executables; } + private buildRuleUrl(spaceId: string): string | undefined { + if (!this.taskRunnerContext.kibanaBaseUrl) { + return; + } + + try { + const ruleUrl = new URL( + `${ + spaceId !== 'default' ? `/s/${spaceId}` : '' + }${triggersActionsRoute}${getRuleDetailsRoute(this.rule.id)}`, + this.taskRunnerContext.kibanaBaseUrl + ); + + return ruleUrl.toString(); + } catch (error) { + this.logger.debug( + `Rule "${this.rule.id}" encountered an error while constructing the rule.url variable: ${error.message}` + ); + return; + } + } + private async actionRunOrAddToBulk({ enqueueOptions, bulkActions, @@ -359,7 +383,7 @@ export class ExecutionHandler< } = this; const muted = mutedAlertIdsSet!.has(alertId); - const throttled = alert.isThrottled(throttle); + const throttled = alert.isThrottled(throttle ?? null); if (muted) { if ( diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index a06bd01cb9ce07..3fc70537f1bc96 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -218,8 +218,8 @@ export class TaskRunner< alertTypeId: ruleTypeId, consumer, schedule, - throttle, - notifyWhen, + throttle = null, + notifyWhen = null, name, tags, createdBy, diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts index 61ec54c48886fb..1b44b534a42716 100644 --- a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts @@ -30,6 +30,7 @@ interface TransformActionParamsOptions { state: AlertInstanceState; kibanaBaseUrl?: string; context: AlertInstanceContext; + ruleUrl?: string; } export function transformActionParams({ @@ -49,6 +50,7 @@ export function transformActionParams({ state, kibanaBaseUrl, alertParams, + ruleUrl, }: TransformActionParamsOptions): RuleActionParams { // when the list of variables we pass in here changes, // the UI will need to be updated as well; see: @@ -72,6 +74,7 @@ export function transformActionParams({ type: alertType, spaceId, tags, + url: ruleUrl, }, alert: { id: alertInstanceId, diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 3eccd1d3621276..c2af1555cfa367 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -43,6 +43,7 @@ import { RuleMonitoring, MappedParams, RuleSnooze, + IntervalSchedule, RuleLastRun, } from '../common'; import { PublicAlertFactory } from './alert/create_alert_factory'; @@ -208,6 +209,11 @@ export interface RawRuleAction extends SavedObjectAttributes { actionRef: string; actionTypeId: string; params: RuleActionParams; + frequency?: { + summary: boolean; + notifyWhen: RuleNotifyWhenType; + throttle: string | null; + }; } export interface RuleMeta extends SavedObjectAttributes { @@ -256,7 +262,7 @@ export interface RawRule extends SavedObjectAttributes { alertTypeId: string; // this cannot be renamed since it is in the saved object consumer: string; legacyId: string | null; - schedule: SavedObjectAttributes; + schedule: IntervalSchedule; actions: RawRuleAction[]; params: SavedObjectAttributes; mapped_params?: MappedParams; @@ -267,8 +273,8 @@ export interface RawRule extends SavedObjectAttributes { updatedAt: string; apiKey: string | null; apiKeyOwner: string | null; - throttle: string | null; - notifyWhen: RuleNotifyWhenType | null; + throttle?: string | null; + notifyWhen?: RuleNotifyWhenType | null; muteAll: boolean; mutedInstanceIds: string[]; meta?: RuleMeta; diff --git a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap index 508d8aac06dad9..a154ededc2a332 100644 --- a/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/apm_telemetry.test.ts.snap @@ -1040,6 +1040,72 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the }, "indices": { "properties": { + "metric": { + "properties": { + "shards": { + "properties": { + "total": { + "type": "long" + } + } + }, + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "traces": { + "properties": { + "shards": { + "properties": { + "total": { + "type": "long" + } + } + }, + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, "shards": { "properties": { "total": { @@ -1086,6 +1152,12 @@ exports[`APM telemetry helpers getApmTelemetry generates a JSON object with the "service_id": { "type": "keyword" }, + "num_service_nodes": { + "type": "long" + }, + "num_transaction_types": { + "type": "long" + }, "timed_out": { "type": "boolean" }, diff --git a/x-pack/plugins/apm/common/correlations/failed_transactions_correlations/types.ts b/x-pack/plugins/apm/common/correlations/failed_transactions_correlations/types.ts index fdcac2b96ab80e..be83646b73a3c2 100644 --- a/x-pack/plugins/apm/common/correlations/failed_transactions_correlations/types.ts +++ b/x-pack/plugins/apm/common/correlations/failed_transactions_correlations/types.ts @@ -8,7 +8,6 @@ import { FieldValuePair, HistogramItem } from '../types'; import { CORRELATIONS_IMPACT_THRESHOLD } from './constants'; -import { FieldStats } from '../field_stats_types'; export interface FailedTransactionsCorrelation extends FieldValuePair { doc_count: number; @@ -31,6 +30,5 @@ export interface FailedTransactionsCorrelationsResponse { overallHistogram?: HistogramItem[]; totalDocCount?: number; errorHistogram?: HistogramItem[]; - fieldStats?: FieldStats[]; fallbackResult?: FailedTransactionsCorrelation; } diff --git a/x-pack/plugins/apm/common/correlations/field_stats_types.ts b/x-pack/plugins/apm/common/correlations/field_stats_types.ts index b350a0ff9e6392..51a601dd39c18b 100644 --- a/x-pack/plugins/apm/common/correlations/field_stats_types.ts +++ b/x-pack/plugins/apm/common/correlations/field_stats_types.ts @@ -31,24 +31,4 @@ export interface TopValuesStats { topValuesSamplerShardSize?: number; } -export interface NumericFieldStats extends TopValuesStats { - min: number; - max: number; - avg: number; - median?: number; -} - -export type KeywordFieldStats = TopValuesStats; - -export interface BooleanFieldStats { - fieldName: string; - count: number; - [key: string]: number | string; -} - -export type FieldStats = - | NumericFieldStats - | KeywordFieldStats - | BooleanFieldStats; - export type FieldValueFieldStats = TopValuesStats; diff --git a/x-pack/plugins/apm/common/correlations/latency_correlations/types.ts b/x-pack/plugins/apm/common/correlations/latency_correlations/types.ts index 8a612145e5d126..80067337a8b337 100644 --- a/x-pack/plugins/apm/common/correlations/latency_correlations/types.ts +++ b/x-pack/plugins/apm/common/correlations/latency_correlations/types.ts @@ -6,7 +6,6 @@ */ import { FieldValuePair, HistogramItem } from '../types'; -import { FieldStats } from '../field_stats_types'; export interface LatencyCorrelation extends FieldValuePair { correlation: number; @@ -21,5 +20,4 @@ export interface LatencyCorrelationsResponse { overallHistogram?: HistogramItem[]; percentileThresholdValue?: number | null; latencyCorrelations?: LatencyCorrelation[]; - fieldStats?: FieldStats[]; } diff --git a/x-pack/plugins/apm/common/utils/term_query.ts b/x-pack/plugins/apm/common/utils/term_query.ts new file mode 100644 index 00000000000000..07e20392da0980 --- /dev/null +++ b/x-pack/plugins/apm/common/utils/term_query.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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { isEmpty, isNil } from 'lodash'; + +export function termQuery( + field: T, + value: string | boolean | number | undefined | null +): QueryDslQueryContainer[] { + if (isNil(value) || isEmpty(value)) { + return []; + } + + return [{ term: { [field]: value } }]; +} diff --git a/x-pack/plugins/apm/dev_docs/testing.md b/x-pack/plugins/apm/dev_docs/testing.md index 62fec34701cf92..8362e0c8f74516 100644 --- a/x-pack/plugins/apm/dev_docs/testing.md +++ b/x-pack/plugins/apm/dev_docs/testing.md @@ -34,7 +34,7 @@ The API tests are located in [`x-pack/test/apm_api_integration/`](/x-pack/test/a ### Start server and run test in a single process ``` -node scripts/test/api [--trial/--basic] [--help] +node x-pack/plugins/apm/scripts/test/api [--trial/--basic] [--help] ``` The above command will start an ES instance on http://localhost:9220, a Kibana instance on http://localhost:5620 and run the api tests. @@ -45,10 +45,10 @@ Once the tests finish, the instances will be terminated. ```sh # start server -node scripts/test/api --server --basic +node x-pack/plugins/apm/scripts/test/api --server --basic # run tests -node scripts/test/api --runner --basic --grep-files=error_group_list +node x-pack/plugins/apm/scripts/test/api --runner --basic --grep-files=error_group_list ``` ### Update snapshots (from Kibana root) @@ -77,13 +77,13 @@ The E2E tests are located in [`x-pack/plugins/apm/ftr_e2e`](../ftr_e2e) ### Start test server ``` -node x-pack/plugins/apm/scripts/test/e2e.js --server +node x-pack/plugins/apm/scripts/test/e2e --server ``` ### Run tests ``` -node x-pack/plugins/apm/scripts/test/e2e.js --runner --open +node x-pack/plugins/apm/scripts/test/e2e --runner --open ``` ### A11y checks diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index ff8f2c24cfd93a..3df051925ab619 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -21,6 +21,7 @@ "unifiedSearch", "dataViews", "advancedSettings", + "unifiedFieldList", "lens", "maps" ], @@ -28,8 +29,10 @@ "actions", "alerting", "cases", + "charts", "cloud", "fleet", + "fieldFormats", "home", "ml", "security", @@ -52,4 +55,4 @@ "esUiShared", "maps" ] -} \ No newline at end of file +} diff --git a/x-pack/plugins/apm/public/components/app/correlations/context_popover/context_popover.tsx b/x-pack/plugins/apm/public/components/app/correlations/context_popover/context_popover.tsx deleted file mode 100644 index 6182f4f08aa3b5..00000000000000 --- a/x-pack/plugins/apm/public/components/app/correlations/context_popover/context_popover.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiPopover, - EuiPopoverTitle, - EuiTitle, - EuiToolTip, -} from '@elastic/eui'; -import React, { useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FieldStats } from '../../../../../common/correlations/field_stats_types'; -import { OnAddFilter, TopValues } from './top_values'; -import { useTheme } from '../../../../hooks/use_theme'; - -export function CorrelationsContextPopover({ - fieldName, - fieldValue, - topValueStats, - onAddFilter, -}: { - fieldName: string; - fieldValue: string | number; - topValueStats?: FieldStats; - onAddFilter: OnAddFilter; -}) { - const [infoIsOpen, setInfoOpen] = useState(false); - const theme = useTheme(); - - if (!topValueStats) return null; - - const popoverTitle = ( - - - -
{fieldName}
-
-
-
- ); - - return ( - - ) => { - setInfoOpen(!infoIsOpen); - }} - aria-label={i18n.translate( - 'xpack.apm.correlations.fieldContextPopover.topFieldValuesAriaLabel', - { - defaultMessage: 'Show top 10 field values', - } - )} - data-test-subj={'apmCorrelationsContextPopoverButton'} - style={{ marginLeft: theme.eui.euiSizeXS }} - /> - - } - isOpen={infoIsOpen} - closePopover={() => setInfoOpen(false)} - anchorPosition="rightCenter" - data-test-subj={'apmCorrelationsContextPopover'} - > - {popoverTitle} - -
- {i18n.translate( - 'xpack.apm.correlations.fieldContextPopover.fieldTopValuesLabel', - { - defaultMessage: 'Top 10 values', - } - )} -
-
- {infoIsOpen ? ( - - ) : null} -
- ); -} diff --git a/x-pack/plugins/apm/public/components/app/correlations/context_popover/field_stats_popover.tsx b/x-pack/plugins/apm/public/components/app/correlations/context_popover/field_stats_popover.tsx new file mode 100644 index 00000000000000..6bc7ae8fecf0fc --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/context_popover/field_stats_popover.tsx @@ -0,0 +1,338 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + FieldPopover, + FieldStats, + FieldPopoverHeader, + FieldStatsServices, + FieldStatsProps, + FieldStatsState, +} from '@kbn/unified-field-list-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; +import { FieldTopValuesBucket } from '@kbn/unified-field-list-plugin/public'; + +import type { FieldTopValuesBucketParams } from '@kbn/unified-field-list-plugin/public'; +import { + EuiHorizontalRule, + EuiText, + EuiSpacer, + EuiLoadingSpinner, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import numeral from '@elastic/numeral'; +import { termQuery } from '../../../../../common/utils/term_query'; +import { + SERVICE_NAME, + TRANSACTION_NAME, + TRANSACTION_TYPE, +} from '../../../../../common/elasticsearch_fieldnames'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useFetchParams } from '../use_fetch_params'; +import type { ApmPluginStartDeps } from '../../../../plugin'; +import { useApmDataView } from '../../../../hooks/use_apm_data_view'; +import { useTheme } from '../../../../hooks/use_theme'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; + +const HIGHLIGHTED_BUCKET_PROPS = { + color: 'accent', + textProps: { + color: 'accent', + }, +}; +export function kqlQuery(kql: string): estypes.QueryDslQueryContainer[] { + return !!kql ? [toElasticsearchQuery(fromKueryExpression(kql))] : []; +} + +export type OnAddFilter = ({ + fieldName, + fieldValue, + include, +}: { + fieldName: string; + fieldValue: string | number; + include: boolean; +}) => void; + +type FieldStatsPopoverContentProps = Omit< + FieldStatsProps, + 'dataViewOrDataViewId' +> & { + fieldName: string; + fieldValue: string | number; + dslQuery: object; + dataView: DataView; +}; + +export function FieldStatsPopoverContent({ + fieldName, + fieldValue, + services, + field, + dataView, + dslQuery, + filters, + fromDate, + toDate, + onAddFilter, + overrideFieldTopValueBar, +}: FieldStatsPopoverContentProps) { + const [needToFetchIndividualStat, setNeedToFetchIndividualStat] = + useState(false); + + const onStateChange = useCallback( + (nextState: FieldStatsState) => { + const { topValues } = nextState; + const idxToHighlight = Array.isArray(topValues) + ? topValues.findIndex((value) => value.key === fieldValue) + : null; + + setNeedToFetchIndividualStat( + idxToHighlight === -1 && + fieldName !== undefined && + fieldValue !== undefined + ); + }, + [fieldName, fieldValue] + ); + + const params = useFetchParams(); + const { data: fieldValueStats, status } = useFetcher( + (callApmApi) => { + if (needToFetchIndividualStat) { + return callApmApi( + 'GET /internal/apm/correlations/field_value_stats/transactions', + { + params: { + query: { + ...params, + fieldName, + fieldValue, + // Using sampler shard size to match with unified field list's default + samplerShardSize: '5000', + }, + }, + } + ); + } + }, + [params, fieldName, fieldValue, needToFetchIndividualStat] + ); + const progressBarMax = fieldValueStats?.topValuesSampleSize; + const formatter = dataView.getFormatterForField(field); + + return ( + <> + + {needToFetchIndividualStat ? ( + <> + + + + + + {status === FETCH_STATUS.SUCCESS && + Array.isArray(fieldValueStats?.topValues) ? ( + fieldValueStats?.topValues.map((value) => { + if (progressBarMax === undefined) return null; + + const formatted = formatter.convert(fieldValue); + const decimal = value.doc_count / progressBarMax; + const valueText = + progressBarMax !== undefined + ? numeral(decimal).format('0.0%') + : ''; + + return ( + + ); + }) + ) : ( + + + + )} + + ) : null} + + ); +} +export function FieldStatsPopover({ + fieldName, + fieldValue, + onAddFilter, +}: { + fieldName: string; + fieldValue: string | number; + onAddFilter: OnAddFilter; +}) { + const { + query: { kuery: kql }, + } = useApmParams('/services/{serviceName}'); + + const { start, end } = useFetchParams(); + + const { + data, + core: { uiSettings }, + } = useApmPluginContext(); + const { dataView } = useApmDataView(); + const { + services: { fieldFormats, charts }, + } = useKibana(); + + const [infoIsOpen, setInfoOpen] = useState(false); + const field = dataView?.getFieldByName(fieldName); + + const closePopover = useCallback(() => setInfoOpen(false), []); + const theme = useTheme(); + + const params = useFetchParams(); + + const fieldStatsQuery = useMemo(() => { + const dslQuery = kqlQuery(kql); + return { + bool: { + filter: [ + ...termQuery(SERVICE_NAME, params.serviceName), + ...termQuery(TRANSACTION_TYPE, params.transactionType), + ...termQuery(TRANSACTION_NAME, params.transactionName), + ...dslQuery, + ], + }, + }; + }, [params, kql]); + + const fieldStatsServices: Partial = useMemo( + () => ({ + uiSettings, + dataViews: data.dataViews, + data, + fieldFormats, + charts, + }), + [uiSettings, data, fieldFormats, charts] + ); + + const addFilter = useCallback( + ( + popoverField: DataViewField | '_exists_', + value: unknown, + type: '+' | '-' + ) => { + if ( + popoverField !== '_exists_' && + (typeof value === 'number' || typeof value === 'string') + ) { + onAddFilter({ + fieldName: popoverField.name, + fieldValue: value, + include: type === '+', + }); + } + }, + [onAddFilter] + ); + + const overrideFieldTopValueBar = useCallback( + (fieldTopValuesBucketParams: FieldTopValuesBucketParams) => { + if (fieldTopValuesBucketParams.type === 'other') { + return { color: 'primary' }; + } + return fieldValue === fieldTopValuesBucketParams.fieldValue + ? HIGHLIGHTED_BUCKET_PROPS + : {}; + }, + [fieldValue] + ); + + if (!fieldFormats || !charts || !field || !dataView) return null; + + const trigger = ( + + ) => { + setInfoOpen(!infoIsOpen); + }} + aria-label={i18n.translate( + 'xpack.apm.correlations.fieldContextPopover.topFieldValuesAriaLabel', + { + defaultMessage: 'Show top 10 field values', + } + )} + data-test-subj={'apmCorrelationsContextPopoverButton'} + style={{ marginLeft: theme.eui.euiSizeXS }} + /> + + ); + + return ( + ( + + )} + renderContent={() => ( + <> + + + )} + /> + ); +} diff --git a/x-pack/plugins/apm/public/components/app/correlations/context_popover/index.ts b/x-pack/plugins/apm/public/components/app/correlations/context_popover/index.ts index 5588328da44527..c982a7dd397ea6 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/context_popover/index.ts +++ b/x-pack/plugins/apm/public/components/app/correlations/context_popover/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { CorrelationsContextPopover } from './context_popover'; +export { FieldStatsPopover } from './field_stats_popover'; diff --git a/x-pack/plugins/apm/public/components/app/correlations/context_popover/top_values.tsx b/x-pack/plugins/apm/public/components/app/correlations/context_popover/top_values.tsx deleted file mode 100644 index aa5547fc039672..00000000000000 --- a/x-pack/plugins/apm/public/components/app/correlations/context_popover/top_values.tsx +++ /dev/null @@ -1,307 +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 { - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiProgress, - EuiSpacer, - EuiToolTip, - EuiText, - EuiHorizontalRule, - EuiLoadingSpinner, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import numeral from '@elastic/numeral'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { - FieldStats, - TopValueBucket, -} from '../../../../../common/correlations/field_stats_types'; -import { asPercent } from '../../../../../common/utils/formatters'; -import { useTheme } from '../../../../hooks/use_theme'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { useFetchParams } from '../use_fetch_params'; - -export type OnAddFilter = ({ - fieldName, - fieldValue, - include, -}: { - fieldName: string; - fieldValue: string | number; - include: boolean; -}) => void; - -interface TopValueProps { - progressBarMax: number; - barColor: string; - value: TopValueBucket; - isHighlighted: boolean; - fieldName: string; - onAddFilter?: OnAddFilter; - valueText?: string; - reverseLabel?: boolean; -} -export function TopValue({ - progressBarMax, - barColor, - value, - isHighlighted, - fieldName, - onAddFilter, - valueText, - reverseLabel = false, -}: TopValueProps) { - const theme = useTheme(); - return ( - - - - {value.key} - - } - className="eui-textTruncate" - aria-label={value.key.toString()} - valueText={valueText} - labelProps={ - isHighlighted - ? { - style: { fontWeight: 'bold' }, - } - : undefined - } - /> - - {fieldName !== undefined && - value.key !== undefined && - onAddFilter !== undefined ? ( - <> - { - onAddFilter({ - fieldName, - fieldValue: - typeof value.key === 'number' - ? value.key.toString() - : value.key, - include: true, - }); - }} - aria-label={i18n.translate( - 'xpack.apm.correlations.fieldContextPopover.addFilterAriaLabel', - { - defaultMessage: 'Filter for {fieldName}: "{value}"', - values: { fieldName, value: value.key }, - } - )} - data-test-subj={`apmFieldContextTopValuesAddFilterButton-${value.key}-${value.key}`} - style={{ - minHeight: 'auto', - width: theme.eui.euiSizeL, - paddingRight: 2, - paddingLeft: 2, - paddingTop: 0, - paddingBottom: 0, - }} - /> - { - onAddFilter({ - fieldName, - fieldValue: - typeof value.key === 'number' - ? value.key.toString() - : value.key, - include: false, - }); - }} - aria-label={i18n.translate( - 'xpack.apm.correlations.fieldContextPopover.removeFilterAriaLabel', - { - defaultMessage: 'Filter out {fieldName}: "{value}"', - values: { fieldName, value: value.key }, - } - )} - data-test-subj={`apmFieldContextTopValuesExcludeFilterButton-${value.key}-${value.key}`} - style={{ - minHeight: 'auto', - width: theme.eui.euiSizeL, - paddingTop: 0, - paddingBottom: 0, - paddingRight: 2, - paddingLeft: 2, - }} - /> - - ) : null} - - ); -} - -interface TopValuesProps { - topValueStats: FieldStats; - compressed?: boolean; - onAddFilter?: OnAddFilter; - fieldValue?: string | number; -} - -export function TopValues({ - topValueStats, - onAddFilter, - fieldValue, -}: TopValuesProps) { - const { topValues, topValuesSampleSize, count, fieldName } = topValueStats; - const theme = useTheme(); - - const idxToHighlight = Array.isArray(topValues) - ? topValues.findIndex((value) => value.key === fieldValue) - : null; - - const params = useFetchParams(); - const { data: fieldValueStats, status } = useFetcher( - (callApmApi) => { - if ( - idxToHighlight === -1 && - fieldName !== undefined && - fieldValue !== undefined - ) { - return callApmApi( - 'GET /internal/apm/correlations/field_value_stats/transactions', - { - params: { - query: { - ...params, - fieldName, - fieldValue, - }, - }, - } - ); - } - }, - [params, fieldName, fieldValue, idxToHighlight] - ); - if ( - !Array.isArray(topValues) || - topValues?.length === 0 || - fieldValue === undefined - ) - return null; - - const sampledSize = - typeof topValuesSampleSize === 'string' - ? parseInt(topValuesSampleSize, 10) - : topValuesSampleSize; - - const progressBarMax = sampledSize ?? count; - return ( -
- {Array.isArray(topValues) && - topValues.map((value) => { - const isHighlighted = - fieldValue !== undefined && value.key === fieldValue; - const barColor = isHighlighted ? 'accent' : 'primary'; - const valueText = - progressBarMax !== undefined - ? numeral(value.doc_count / progressBarMax).format('0.0%') // asPercent(value.doc_count, progressBarMax) - : undefined; - - return ( - <> - - - - ); - })} - - {idxToHighlight === -1 && ( - <> - - - - - - {status === FETCH_STATUS.SUCCESS && - Array.isArray(fieldValueStats?.topValues) ? ( - fieldValueStats?.topValues.map((value) => { - const valueText = - progressBarMax !== undefined - ? asPercent(value.doc_count, progressBarMax) - : undefined; - - return ( - - ); - }) - ) : ( - - - - )} - - )} - - {topValueStats.topValuesSampleSize !== undefined && ( - <> - - - {i18n.translate( - 'xpack.apm.correlations.fieldContextPopover.calculatedFromSampleDescription', - { - defaultMessage: - 'Calculated from sample of {sampleSize} documents', - values: { sampleSize: topValueStats.topValuesSampleSize }, - } - )} - - - )} -
- ); -} diff --git a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx index 8ca7c096d994eb..af9dccbc4c6723 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx @@ -29,12 +29,12 @@ import { i18n } from '@kbn/i18n'; import { useUiTracker } from '@kbn/observability-plugin/public'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { FieldStatsPopover } from './context_popover/field_stats_popover'; import { asPercent, asPreciseDecimal, } from '../../../../common/utils/formatters'; import { FailedTransactionsCorrelation } from '../../../../common/correlations/failed_transactions_correlations/types'; -import { FieldStats } from '../../../../common/correlations/field_stats_types'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useLocalStorage } from '../../../hooks/use_local_storage'; @@ -50,8 +50,7 @@ import { DurationDistributionChart } from '../../shared/charts/duration_distribu import { CorrelationsEmptyStatePrompt } from './empty_state_prompt'; import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning'; import { CorrelationsProgressControls } from './progress_controls'; -import { CorrelationsContextPopover } from './context_popover'; -import { OnAddFilter } from './context_popover/top_values'; +import { OnAddFilter } from './context_popover/field_stats_popover'; import { useFailedTransactionsCorrelations } from './use_failed_transactions_correlations'; import { getTransactionDistributionChartData } from './get_transaction_distribution_chart_data'; @@ -74,13 +73,6 @@ export function FailedTransactionsCorrelations({ const { progress, response, startFetch, cancelFetch } = useFailedTransactionsCorrelations(); - const fieldStats: Record | undefined = useMemo(() => { - return response.fieldStats?.reduce((obj, field) => { - obj[field.fieldName] = field; - return obj; - }, {} as Record); - }, [response?.fieldStats]); - const { overallHistogram, hasData, status } = getOverallHistogram( response, progress.isRunning @@ -295,10 +287,9 @@ export function FailedTransactionsCorrelations({ render: (_, { fieldName, fieldValue }) => ( <> {fieldName} - @@ -363,7 +354,7 @@ export function FailedTransactionsCorrelations({ ], }, ] as Array>; - }, [fieldStats, onAddFilter, showStats]); + }, [onAddFilter, showStats]); useEffect(() => { if (progress.error) { diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx index 35941d960d62ef..0a53cb0f3b40f4 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx @@ -28,9 +28,9 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useUiTracker } from '@kbn/observability-plugin/public'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { FieldStatsPopover } from './context_popover/field_stats_popover'; import { asPreciseDecimal } from '../../../../common/utils/formatters'; import { LatencyCorrelation } from '../../../../common/correlations/latency_correlations/types'; -import { FieldStats } from '../../../../common/correlations/field_stats_types'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; @@ -45,8 +45,7 @@ import { getOverallHistogram } from './utils/get_overall_histogram'; import { CorrelationsEmptyStatePrompt } from './empty_state_prompt'; import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning'; import { CorrelationsProgressControls } from './progress_controls'; -import { CorrelationsContextPopover } from './context_popover'; -import { OnAddFilter } from './context_popover/top_values'; +import { OnAddFilter } from './context_popover/field_stats_popover'; import { useLatencyCorrelations } from './use_latency_correlations'; import { getTransactionDistributionChartData } from './get_transaction_distribution_chart_data'; import { useTheme } from '../../../hooks/use_theme'; @@ -79,13 +78,6 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { progress.isRunning ); - const fieldStats: Record | undefined = useMemo(() => { - return response.fieldStats?.reduce((obj, field) => { - obj[field.fieldName] = field; - return obj; - }, {} as Record); - }, [response?.fieldStats]); - useEffect(() => { if (progress.error) { notifications.toasts.addDanger({ @@ -201,10 +193,9 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { render: (_, { fieldName, fieldValue }) => ( <> {fieldName} - @@ -266,7 +257,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { ), }, ], - [fieldStats, onAddFilter] + [onAddFilter] ); const [sortField, setSortField] = 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 00c768fc1abbfd..0a71f4c28e5a1e 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 @@ -68,13 +68,6 @@ function wrapper({ }, ], }; - case 'POST /internal/apm/correlations/field_stats/transactions': - return { - stats: [ - { fieldName: 'field-name-1', count: 123 }, - { fieldName: 'field-name-2', count: 1111 }, - ], - }; default: return {}; } @@ -168,7 +161,7 @@ describe('useFailedTransactionsCorrelations', () => { ); try { - // Each simulated request takes 100ms. After an inital 50ms + // Each simulated request takes 100ms. After an initial 50ms // we track the internal requests the hook is running and // check the expected progress after these requests. jest.advanceTimersByTime(50); @@ -183,7 +176,6 @@ describe('useFailedTransactionsCorrelations', () => { }); expect(result.current.response).toEqual({ ccsWarning: false, - fieldStats: undefined, errorHistogram: undefined, failedTransactionsCorrelations: undefined, overallHistogram: [ @@ -205,7 +197,6 @@ describe('useFailedTransactionsCorrelations', () => { }); expect(result.current.response).toEqual({ ccsWarning: false, - fieldStats: undefined, errorHistogram: [ { doc_count: 1234, @@ -233,47 +224,6 @@ describe('useFailedTransactionsCorrelations', () => { loaded: 0.15, }); - jest.advanceTimersByTime(100); - await waitFor(() => expect(result.current.progress.loaded).toBe(0.9)); - - expect(result.current.progress).toEqual({ - error: undefined, - isRunning: true, - loaded: 0.9, - }); - - expect(result.current.response).toEqual({ - ccsWarning: false, - fieldStats: undefined, - errorHistogram: [ - { - doc_count: 1234, - key: 'the-key', - }, - ], - failedTransactionsCorrelations: [ - { - fieldName: 'field-name-1', - fieldValue: 'field-value-1', - doc_count: 123, - bg_count: 1234, - score: 0.66, - pValue: 0.01, - normalizedScore: 0.85, - failurePercentage: 30, - successPercentage: 70, - histogram: [{ key: 'the-key', doc_count: 123 }], - }, - ], - overallHistogram: [ - { - doc_count: 1234, - key: 'the-key', - }, - ], - percentileThresholdValue: 1.234, - }); - jest.advanceTimersByTime(100); await waitFor(() => expect(result.current.progress.loaded).toBe(1)); @@ -285,10 +235,6 @@ describe('useFailedTransactionsCorrelations', () => { expect(result.current.response).toEqual({ ccsWarning: false, - fieldStats: [ - { fieldName: 'field-name-1', count: 123 }, - { fieldName: 'field-name-2', count: 1111 }, - ], errorHistogram: [ { doc_count: 1234, diff --git a/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.ts b/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.ts index f9c365c539e0f9..59c594c04b0c5a 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.ts +++ b/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.ts @@ -73,7 +73,6 @@ export function useFailedTransactionsCorrelations() { overallHistogram: undefined, totalDocCount: undefined, errorHistogram: undefined, - fieldStats: undefined, }); setResponse.flush(); @@ -249,22 +248,6 @@ export function useFailedTransactionsCorrelations() { } } - setResponse.flush(); - - const { stats } = await callApmApi( - 'POST /internal/apm/correlations/field_stats/transactions', - { - signal: abortCtrl.current.signal, - params: { - body: { - ...fetchParams, - fieldsToSample: [...fieldsToSample], - }, - }, - } - ); - - responseUpdate.fieldStats = stats; setResponse({ ...responseUpdate, fallbackResult, 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 a09530960589c9..57b67da649ceb8 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 @@ -62,13 +62,6 @@ function wrapper({ }, ], }; - case 'POST /internal/apm/correlations/field_stats/transactions': - return { - stats: [ - { fieldName: 'field-name-1', count: 123 }, - { fieldName: 'field-name-2', count: 1111 }, - ], - }; default: return {}; } @@ -164,7 +157,6 @@ describe('useLatencyCorrelations', () => { }); expect(result.current.response).toEqual({ ccsWarning: false, - fieldStats: undefined, latencyCorrelations: undefined, overallHistogram: [ { @@ -200,38 +192,6 @@ describe('useLatencyCorrelations', () => { jest.advanceTimersByTime(100); await waitFor(() => expect(result.current.progress.loaded).toBe(1)); - expect(result.current.progress).toEqual({ - error: undefined, - isRunning: true, - loaded: 1, - }); - - expect(result.current.response).toEqual({ - ccsWarning: false, - fieldStats: undefined, - latencyCorrelations: [ - { - fieldName: 'field-name-1', - fieldValue: 'field-value-1', - correlation: 0.5, - histogram: [{ key: 'the-key', doc_count: 123 }], - ksTest: 0.001, - }, - ], - overallHistogram: [ - { - doc_count: 1234, - key: 'the-key', - }, - ], - percentileThresholdValue: 1.234, - }); - - jest.advanceTimersByTime(100); - await waitFor(() => - expect(result.current.response.fieldStats).toBeDefined() - ); - expect(result.current.progress).toEqual({ error: undefined, isRunning: false, @@ -240,10 +200,6 @@ describe('useLatencyCorrelations', () => { expect(result.current.response).toEqual({ ccsWarning: false, - fieldStats: [ - { fieldName: 'field-name-1', count: 123 }, - { fieldName: 'field-name-2', count: 1111 }, - ], latencyCorrelations: [ { fieldName: 'field-name-1', diff --git a/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.ts b/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.ts index b2ba6fc4c68b15..78013f421a87c0 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.ts +++ b/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.ts @@ -73,7 +73,6 @@ export function useLatencyCorrelations() { percentileThresholdValue: undefined, overallHistogram: undefined, totalDocCount: undefined, - fieldStats: undefined, }); setResponse.flush(); @@ -257,20 +256,6 @@ export function useLatencyCorrelations() { } setResponse.flush(); - const { stats } = await callApmApi( - 'POST /internal/apm/correlations/field_stats/transactions', - { - signal: abortCtrl.current.signal, - params: { - body: { - ...fetchParams, - fieldsToSample: [...fieldsToSample], - }, - }, - } - ); - - responseUpdate.fieldStats = stats; setResponse({ ...responseUpdate, loaded: LOADED_DONE, diff --git a/x-pack/plugins/apm/public/components/app/infra_overview/infra_tabs/index.tsx b/x-pack/plugins/apm/public/components/app/infra_overview/infra_tabs/index.tsx index 09f6eaf56877b0..02c6008d9e6041 100644 --- a/x-pack/plugins/apm/public/components/app/infra_overview/infra_tabs/index.tsx +++ b/x-pack/plugins/apm/public/components/app/infra_overview/infra_tabs/index.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import { EuiTabbedContent, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiLoadingSpinner, EuiTabs, EuiTab } from '@elastic/eui'; import React from 'react'; +import { useHistory } from 'react-router-dom'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { useTimeRange } from '../../../../hooks/use_time_range'; @@ -14,6 +15,7 @@ import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { EmptyPrompt } from './empty_prompt'; import { FailurePrompt } from './failure_prompt'; import { useTabs } from './use_tabs'; +import { push } from '../../../shared/links/url_helpers'; const INITIAL_STATE = { containerIds: [], @@ -23,8 +25,9 @@ const INITIAL_STATE = { export function InfraTabs() { const { serviceName } = useApmServiceContext(); + const history = useHistory(); const { - query: { environment, kuery, rangeFrom, rangeTo }, + query: { environment, kuery, rangeFrom, rangeTo, detailTab }, } = useApmParams('/services/{serviceName}/infrastructure'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); @@ -89,13 +92,30 @@ export function InfraTabs() { ); } + const currentTab = tabs.find(({ id }) => id === detailTab) ?? tabs[0]; + return ( <> - + + {tabs.map(({ id, name }) => { + return ( + { + push(history, { + query: { + detailTab: id, + }, + }); + }} + isSelected={currentTab.id === id} + id={id} + > + {name} + + ); + })} + + {currentTab.content} ); } diff --git a/x-pack/plugins/apm/public/components/app/infra_overview/infra_tabs/use_tabs.tsx b/x-pack/plugins/apm/public/components/app/infra_overview/infra_tabs/use_tabs.tsx index 0d593d2b600096..e5244981f110b1 100644 --- a/x-pack/plugins/apm/public/components/app/infra_overview/infra_tabs/use_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/infra_overview/infra_tabs/use_tabs.tsx @@ -10,6 +10,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import React from 'react'; import { EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { ApmPluginStartDeps } from '../../../../plugin'; type Tab = NonNullable[0] & { @@ -17,6 +18,12 @@ type Tab = NonNullable[0] & { hidden?: boolean; }; +export enum InfraTab { + containers = 'containers', + pods = 'pods', + hosts = 'hosts', +} + export function useTabs({ containerIds, podNames, @@ -102,20 +109,26 @@ export function useTabs({ const tabs: Tab[] = [ { - id: 'containers', - name: 'Containers', + id: InfraTab.containers, + name: i18n.translate('xpack.apm.views.infra.tabs.containers', { + defaultMessage: 'Containers', + }), content: containerMetricsTable, hidden: containerIds && containerIds.length <= 0, }, { - id: 'pods', - name: 'Pods', + id: InfraTab.pods, + name: i18n.translate('xpack.apm.views.infra.tabs.pods', { + defaultMessage: 'Pods', + }), content: podMetricsTable, hidden: podNames && podNames.length <= 0, }, { - id: 'hosts', - name: 'Hosts', + id: InfraTab.hosts, + name: i18n.translate('xpack.apm.views.infra.tabs.hosts', { + defaultMessage: 'Hosts', + }), content: hostMetricsTable, }, ]; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts index 3412decc3f9beb..b64bbb1c0cc5bd 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/use_filters_for_mobile_charts.ts @@ -6,8 +6,7 @@ */ import { useMemo } from 'react'; -import { type QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { isNil, isEmpty } from 'lodash'; +import { termQuery } from '../../../../../common/utils/term_query'; import { environmentQuery } from '../../../../../common/utils/environment_query'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { @@ -19,17 +18,6 @@ import { SERVICE_VERSION, } from '../../../../../common/elasticsearch_fieldnames'; -function termQuery( - field: T, - value: string | boolean | number | undefined | null -): QueryDslQueryContainer[] { - if (isNil(value) || isEmpty(value)) { - return []; - } - - return [{ term: { [field]: value } }]; -} - export function useFiltersForMobileCharts() { const { path: { serviceName }, diff --git a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx index 96600d8b85b5c9..bfff8509b3ab31 100644 --- a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx @@ -21,6 +21,7 @@ import { AlertsOverview } from '../../app/alerts_overview'; import { ErrorGroupDetails } from '../../app/error_group_details'; import { ErrorGroupOverview } from '../../app/error_group_overview'; import { InfraOverview } from '../../app/infra_overview'; +import { InfraTab } from '../../app/infra_overview/infra_tabs/use_tabs'; import { Metrics } from '../../app/metrics'; import { MetricsDetails } from '../../app/metrics_details'; import { ServiceDependencies } from '../../app/service_dependencies'; @@ -320,18 +321,27 @@ export const serviceDetail = { showKueryBar: false, }, }), - '/services/{serviceName}/infrastructure': page({ - tab: 'infrastructure', - title: i18n.translate('xpack.apm.views.infra.title', { - defaultMessage: 'Infrastructure', + '/services/{serviceName}/infrastructure': { + ...page({ + tab: 'infrastructure', + title: i18n.translate('xpack.apm.views.infra.title', { + defaultMessage: 'Infrastructure', + }), + element: , + searchBarOptions: { + showKueryBar: false, + }, }), - element: , - searchBarOptions: { - showKueryBar: false, - showTimeComparison: false, - showTransactionTypeSelector: false, - }, - }), + params: t.partial({ + query: t.partial({ + detailTab: t.union([ + t.literal(InfraTab.containers), + t.literal(InfraTab.pods), + t.literal(InfraTab.hosts), + ]), + }), + }), + }, '/services/{serviceName}/alerts': page({ tab: 'alerts', title: i18n.translate('xpack.apm.views.alerts.title', { diff --git a/x-pack/plugins/apm/public/components/shared/service_icons/icon_popover.tsx b/x-pack/plugins/apm/public/components/shared/service_icons/icon_popover.tsx index ac7c38dc2f8882..8a90a1cffb890f 100644 --- a/x-pack/plugins/apm/public/components/shared/service_icons/icon_popover.tsx +++ b/x-pack/plugins/apm/public/components/shared/service_icons/icon_popover.tsx @@ -55,8 +55,7 @@ export function IconPopover({ ownFocus={false} button={ ; -type TelemetryTaskExecutor = (params: { +export interface TelemetryTaskExecutorParams { + telemetryClient: TelemetryClient; indices: ApmIndicesConfig; - search< - TSearchRequest extends ESSearchRequest & { index: string | string[] } & { - body: { timeout: string }; - } - >( - params: TSearchRequest - ): Promise>; - indicesStats( - params: estypes.IndicesStatsRequest - // promise returned by client has an abort property - // so we cannot use its ReturnType - ): Promise<{ - _all?: { - total?: { store?: { size_in_bytes?: number }; docs?: { count?: number } }; - }; - _shards?: { - total?: number; - }; - }>; - transportRequest: (params: { - path: string; - method: 'get'; - }) => Promise; savedObjectsClient: ISavedObjectsClient; -}) => Promise; +} + +type TelemetryTaskExecutor = ( + params: TelemetryTaskExecutorParams +) => Promise; export interface TelemetryTask { name: string; executor: TelemetryTaskExecutor; } -export type CollectTelemetryParams = Parameters[0] & { +export type CollectTelemetryParams = TelemetryTaskExecutorParams & { isProd: boolean; logger: Logger; }; export function collectDataTelemetry({ - search, indices, logger, - indicesStats, - transportRequest, + telemetryClient, savedObjectsClient, isProd, }: CollectTelemetryParams) { @@ -68,10 +47,8 @@ export function collectDataTelemetry({ try { const time = process.hrtime(); const next = await task.executor({ - search, + telemetryClient, indices, - indicesStats, - transportRequest, savedObjectsClient, }); const took = process.hrtime(time); diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts index 26c0caac5e2d31..babbd57ccc9dc7 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts @@ -68,7 +68,9 @@ describe('data telemetry collection tasks', () => { }, }); - expect(await task?.executor({ search, indices } as any)).toEqual({ + expect( + await task?.executor({ indices, telemetryClient: { search } } as any) + ).toEqual({ environments: { services_with_multiple_environments: 1, services_without_environment: 2, @@ -92,7 +94,9 @@ describe('data telemetry collection tasks', () => { }, }); - expect(await task?.executor({ indices, search } as any)).toEqual({}); + expect( + await task?.executor({ indices, telemetryClient: { search } } as any) + ).toEqual({}); }); }); @@ -135,7 +139,9 @@ describe('data telemetry collection tasks', () => { }); }); - expect(await task?.executor({ indices, search } as any)).toEqual({ + expect( + await task?.executor({ indices, telemetryClient: { search } } as any) + ).toEqual({ aggregated_transactions: { current_implementation: { expected_metric_document_count: 1250, @@ -184,7 +190,9 @@ describe('data telemetry collection tasks', () => { }, }); - expect(await task?.executor({ indices, search } as any)).toEqual({ + expect( + await task?.executor({ indices, telemetryClient: { search } } as any) + ).toEqual({ cloud: { availability_zone: ['us-west-1', 'europe-west1-c'], provider: ['aws', 'gcp'], @@ -197,7 +205,9 @@ describe('data telemetry collection tasks', () => { it('returns an empty map', async () => { const search = jest.fn().mockResolvedValueOnce({}); - expect(await task?.executor({ indices, search } as any)).toEqual({ + expect( + await task?.executor({ indices, telemetryClient: { search } } as any) + ).toEqual({ cloud: { availability_zone: [], provider: [], @@ -224,7 +234,9 @@ describe('data telemetry collection tasks', () => { }, }); - expect(await task?.executor({ indices, search } as any)).toEqual({ + expect( + await task?.executor({ indices, telemetryClient: { search } } as any) + ).toEqual({ host: { os: { platform: ['linux', 'windows', 'macos'] }, }, @@ -235,7 +247,9 @@ describe('data telemetry collection tasks', () => { it('returns an empty map', async () => { const search = jest.fn().mockResolvedValueOnce({}); - expect(await task?.executor({ indices, search } as any)).toEqual({ + expect( + await task?.executor({ indices, telemetryClient: { search } } as any) + ).toEqual({ host: { os: { platform: [], @@ -268,7 +282,9 @@ describe('data telemetry collection tasks', () => { ); }); - expect(await task?.executor({ indices, search } as any)).toEqual({ + expect( + await task?.executor({ indices, telemetryClient: { search } } as any) + ).toEqual({ counts: { error: { '1d': 1, @@ -330,7 +346,10 @@ describe('data telemetry collection tasks', () => { .mockResolvedValueOnce({ body: { count: 1 } }); expect( - await task?.executor({ indices, transportRequest } as any) + await task?.executor({ + indices, + telemetryClient: { transportRequest }, + } as any) ).toEqual({ integrations: { ml: { @@ -345,7 +364,10 @@ describe('data telemetry collection tasks', () => { const transportRequest = jest.fn().mockResolvedValueOnce({}); expect( - await task?.executor({ indices, transportRequest } as any) + await task?.executor({ + indices, + telemetryClient: { transportRequest }, + } as any) ).toEqual({ integrations: { ml: { @@ -361,49 +383,71 @@ describe('data telemetry collection tasks', () => { const task = tasks.find((t) => t.name === 'indices_stats'); it('returns a map of index stats', async () => { - const indicesStats = jest.fn().mockResolvedValueOnce({ + const indicesStats = jest.fn().mockResolvedValue({ _all: { total: { docs: { count: 1 }, store: { size_in_bytes: 1 } } }, _shards: { total: 1 }, }); - expect(await task?.executor({ indices, indicesStats } as any)).toEqual({ + const statsResponse = { + shards: { + total: 1, + }, + all: { + total: { + docs: { + count: 1, + }, + store: { + size_in_bytes: 1, + }, + }, + }, + }; + + expect( + await task?.executor({ + indices, + telemetryClient: { indicesStats }, + } as any) + ).toEqual({ indices: { + ...statsResponse, + metric: statsResponse, + traces: statsResponse, + }, + }); + }); + + describe('with no results', () => { + it('returns zero values', async () => { + const indicesStats = jest.fn().mockResolvedValue({}); + + const statsResponse = { shards: { - total: 1, + total: 0, }, all: { total: { docs: { - count: 1, + count: 0, }, store: { - size_in_bytes: 1, + size_in_bytes: 0, }, }, }, - }, - }); - }); - - describe('with no results', () => { - it('returns zero values', async () => { - const indicesStats = jest.fn().mockResolvedValueOnce({}); + }; - expect(await task?.executor({ indices, indicesStats } as any)).toEqual({ + expect( + await task?.executor({ + indices, + telemetryClient: { indicesStats }, + } as any) + ).toEqual({ indices: { - shards: { - total: 0, - }, - all: { - total: { - docs: { - count: 0, - }, - store: { - size_in_bytes: 0, - }, - }, - }, + ...statsResponse, + metric: statsResponse, + traces: statsResponse, }, }); }); @@ -434,7 +478,9 @@ describe('data telemetry collection tasks', () => { } }); - expect(await task?.executor({ search, indices } as any)).toEqual({ + expect( + await task?.executor({ indices, telemetryClient: { search } } as any) + ).toEqual({ cardinality: { client: { geo: { country_iso_code: { rum: { '1d': 5 } } } }, transaction: { name: { all_agents: { '1d': 3 }, rum: { '1d': 1 } } }, @@ -483,7 +529,11 @@ describe('data telemetry collection tasks', () => { ], }); - expect(await task?.executor({ savedObjectsClient } as any)).toEqual({ + expect( + await task?.executor({ + savedObjectsClient, + } as any) + ).toEqual({ service_groups: { kuery_fields: ['service.environment', 'agent.name'], total: 2, @@ -535,7 +585,11 @@ describe('data telemetry collection tasks', () => { ], }); - expect(await task?.executor({ savedObjectsClient } as any)).toEqual({ + expect( + await task?.executor({ + savedObjectsClient, + } as any) + ).toEqual({ service_groups: { kuery_fields: ['service.environment', 'agent.name'], total: 2, diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts index b89be16b580175..df68cb4155b24c 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts @@ -4,20 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { fromKueryExpression } from '@kbn/es-query'; -import { flatten, merge, sortBy, sum, pickBy, uniq } from 'lodash'; -import { createHash } from 'crypto'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { fromKueryExpression } from '@kbn/es-query'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { asMutableArray } from '../../../../common/utils/as_mutable_array'; +import { createHash } from 'crypto'; +import { flatten, merge, pickBy, sortBy, sum, uniq } from 'lodash'; import { TelemetryTask } from '.'; import { AGENT_NAMES, RUM_AGENT_NAMES } from '../../../../common/agent_name'; -import { - SavedServiceGroup, - APM_SERVICE_GROUP_SAVED_OBJECT_TYPE, - MAX_NUMBER_OF_SERVICE_GROUPS, -} from '../../../../common/service_groups'; -import { getKueryFields } from '../../../../common/utils/get_kuery_fields'; import { AGENT_NAME, AGENT_VERSION, @@ -30,9 +23,9 @@ import { FAAS_TRIGGER_TYPE, HOST_NAME, HOST_OS_PLATFORM, + KUBERNETES_POD_NAME, OBSERVER_HOSTNAME, PARENT_ID, - KUBERNETES_POD_NAME, PROCESSOR_EVENT, SERVICE_ENVIRONMENT, SERVICE_FRAMEWORK_NAME, @@ -40,6 +33,7 @@ import { SERVICE_LANGUAGE_NAME, SERVICE_LANGUAGE_VERSION, SERVICE_NAME, + SERVICE_NODE_NAME, SERVICE_RUNTIME_NAME, SERVICE_RUNTIME_VERSION, SERVICE_VERSION, @@ -48,11 +42,18 @@ import { TRANSACTION_TYPE, USER_AGENT_ORIGINAL, } from '../../../../common/elasticsearch_fieldnames'; +import { + APM_SERVICE_GROUP_SAVED_OBJECT_TYPE, + MAX_NUMBER_OF_SERVICE_GROUPS, + SavedServiceGroup, +} from '../../../../common/service_groups'; +import { asMutableArray } from '../../../../common/utils/as_mutable_array'; +import { getKueryFields } from '../../../../common/utils/get_kuery_fields'; import { APMError } from '../../../../typings/es_schemas/ui/apm_error'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { Span } from '../../../../typings/es_schemas/ui/span'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; -import { APMTelemetry, APMPerService } from '../types'; +import { APMPerService, APMTelemetry } from '../types'; const TIME_RANGES = ['1d', 'all'] as const; type TimeRange = typeof TIME_RANGES[number]; @@ -66,7 +67,7 @@ export const tasks: TelemetryTask[] = [ // adding a composite aggregation on a number of fields and counting the number of buckets. The resulting count is an // approximation of the amount of metric documents that will be created. We record both the expected metric document count plus // the transaction count for that time range. - executor: async ({ indices, search }) => { + executor: async ({ indices, telemetryClient }) => { async function getBucketCountFromPaginatedQuery( sources: estypes.AggregationsCompositeAggregationSource[], prevResult?: { @@ -88,6 +89,7 @@ export const tasks: TelemetryTask[] = [ const params = { index: [indices.transaction], body: { + track_total_hits: true, size: 0, timeout, query: { @@ -98,7 +100,6 @@ export const tasks: TelemetryTask[] = [ ], }, }, - track_total_hits: true, aggs: { transaction_metric_groups: { composite: { @@ -115,7 +116,7 @@ export const tasks: TelemetryTask[] = [ }, }; - const result = await search(params); + const result = await telemetryClient.search(params); let nextAfter: any; @@ -125,12 +126,14 @@ export const tasks: TelemetryTask[] = [ result.aggregations.transaction_metric_groups.buckets.length; } + const transactionCount = result.hits.total.value; + if (nextAfter) { return await getBucketCountFromPaginatedQuery( sources, { expected_metric_document_count, - transaction_count: result.hits.total.value, + transaction_count: transactionCount, }, nextAfter ); @@ -138,14 +141,14 @@ export const tasks: TelemetryTask[] = [ return { expected_metric_document_count, - transaction_count: result.hits.total.value, - ratio: expected_metric_document_count / result.hits.total.value, + transaction_count: transactionCount, + ratio: expected_metric_document_count / transactionCount, }; } // fixed date range for reliable results const lastTransaction = ( - await search({ + await telemetryClient.search({ index: indices.transaction, body: { timeout, @@ -157,6 +160,7 @@ export const tasks: TelemetryTask[] = [ }, }, size: 1, + track_total_hits: false, sort: { '@timestamp': 'desc' as const, }, @@ -244,7 +248,7 @@ export const tasks: TelemetryTask[] = [ }, { name: 'cloud', - executor: async ({ indices, search }) => { + executor: async ({ indices, telemetryClient }) => { function getBucketKeys({ buckets, }: { @@ -260,7 +264,7 @@ export const tasks: TelemetryTask[] = [ const region = 'region'; const provider = 'provider'; - const response = await search({ + const response = await telemetryClient.search({ index: [ indices.error, indices.metric, @@ -268,6 +272,7 @@ export const tasks: TelemetryTask[] = [ indices.transaction, ], body: { + track_total_hits: false, size: 0, timeout, aggs: { @@ -305,7 +310,7 @@ export const tasks: TelemetryTask[] = [ }, { name: 'host', - executor: async ({ indices, search }) => { + executor: async ({ indices, telemetryClient }) => { function getBucketKeys({ buckets, }: { @@ -317,7 +322,7 @@ export const tasks: TelemetryTask[] = [ return buckets.map((bucket) => bucket.key as string); } - const response = await search({ + const response = await telemetryClient.search({ index: [ indices.error, indices.metric, @@ -325,6 +330,7 @@ export const tasks: TelemetryTask[] = [ indices.transaction, ], body: { + track_total_hits: false, size: 0, timeout, aggs: { @@ -352,14 +358,16 @@ export const tasks: TelemetryTask[] = [ }, { name: 'environments', - executor: async ({ indices, search }) => { - const response = await search({ + executor: async ({ indices, telemetryClient }) => { + const response = await telemetryClient.search({ index: [indices.transaction], body: { + track_total_hits: false, + size: 0, timeout, query: { bool: { - filter: [{ range: { '@timestamp': { gte: 'now-1d' } } }], + filter: [range1d], }, }, aggs: { @@ -434,7 +442,7 @@ export const tasks: TelemetryTask[] = [ }, { name: 'processor_events', - executor: async ({ indices, search }) => { + executor: async ({ indices, telemetryClient }) => { const indicesByProcessorEvent = { error: indices.error, metric: indices.metric, @@ -460,10 +468,11 @@ export const tasks: TelemetryTask[] = [ return prevJob.then(async (data) => { const { processorEvent, timeRange } = current; - const totalHitsResponse = await search({ + const totalHitsResponse = await telemetryClient.search({ index: indicesByProcessorEvent[processorEvent], body: { size: 0, + track_total_hits: true, timeout, query: { bool: { @@ -473,15 +482,17 @@ export const tasks: TelemetryTask[] = [ ], }, }, - track_total_hits: true, }, }); const retainmentResponse = timeRange === 'all' - ? await search({ + ? await telemetryClient.search({ index: indicesByProcessorEvent[processorEvent], + size: 10, body: { + track_total_hits: false, + size: 0, timeout, query: { bool: { @@ -530,22 +541,20 @@ export const tasks: TelemetryTask[] = [ }, { name: 'agent_configuration', - executor: async ({ indices, search }) => { - const agentConfigurationCount = ( - await search({ - index: indices.apmAgentConfigurationIndex, - body: { - size: 0, - timeout, - track_total_hits: true, - }, - }) - ).hits.total.value; + executor: async ({ indices, telemetryClient }) => { + const agentConfigurationCount = await telemetryClient.search({ + index: indices.apmAgentConfigurationIndex, + body: { + size: 0, + timeout, + track_total_hits: true, + }, + }); return { counts: { agent_configuration: { - all: agentConfigurationCount, + all: agentConfigurationCount.hits.total.value, }, }, }; @@ -553,11 +562,11 @@ export const tasks: TelemetryTask[] = [ }, { name: 'services', - executor: async ({ indices, search }) => { + executor: async ({ indices, telemetryClient }) => { const servicesPerAgent = await AGENT_NAMES.reduce( (prevJob, agentName) => { return prevJob.then(async (data) => { - const response = await search({ + const response = await telemetryClient.search({ index: [ indices.error, indices.span, @@ -566,6 +575,7 @@ export const tasks: TelemetryTask[] = [ ], body: { size: 0, + track_total_hits: false, timeout, query: { bool: { @@ -606,8 +616,8 @@ export const tasks: TelemetryTask[] = [ }, { name: 'versions', - executor: async ({ search, indices }) => { - const response = await search({ + executor: async ({ indices, telemetryClient }) => { + const response = await telemetryClient.search({ index: [indices.transaction, indices.span, indices.error], terminate_after: 1, body: { @@ -616,6 +626,7 @@ export const tasks: TelemetryTask[] = [ field: 'observer.version', }, }, + track_total_hits: false, size: 1, timeout, sort: { @@ -650,13 +661,14 @@ export const tasks: TelemetryTask[] = [ }, { name: 'groupings', - executor: async ({ search, indices }) => { + executor: async ({ indices, telemetryClient }) => { const errorGroupsCount = ( - await search({ + await telemetryClient.search({ index: indices.error, body: { size: 0, timeout, + track_total_hits: false, query: { bool: { filter: [ @@ -688,9 +700,10 @@ export const tasks: TelemetryTask[] = [ ).aggregations?.top_service.buckets[0]?.error_groups.value; const transactionGroupsCount = ( - await search({ + await telemetryClient.search({ index: indices.transaction, body: { + track_total_hits: false, size: 0, timeout, query: { @@ -724,7 +737,7 @@ export const tasks: TelemetryTask[] = [ ).aggregations?.top_service.buckets[0]?.transaction_groups.value; const tracesPerDayCount = ( - await search({ + await telemetryClient.search({ index: indices.transaction, body: { query: { @@ -746,9 +759,10 @@ export const tasks: TelemetryTask[] = [ ).hits.total.value; const servicesCount = ( - await search({ + await telemetryClient.search({ index: [indices.transaction, indices.error, indices.metric], body: { + track_total_hits: false, size: 0, timeout, query: { @@ -787,10 +801,10 @@ export const tasks: TelemetryTask[] = [ }, { name: 'integrations', - executor: async ({ transportRequest }) => { + executor: async ({ telemetryClient }) => { const apmJobs = ['apm-*', '*-high_mean_response_time']; - const response = (await transportRequest({ + const response = (await telemetryClient.transportRequest({ method: 'get', path: `/_ml/anomaly_detectors/${apmJobs.join(',')}`, })) as { body?: { count: number } }; @@ -806,15 +820,16 @@ export const tasks: TelemetryTask[] = [ }, { name: 'agents', - executor: async ({ search, indices }) => { + executor: async ({ indices, telemetryClient }) => { const size = 3; const agentData = await AGENT_NAMES.reduce(async (prevJob, agentName) => { const data = await prevJob; - const response = await search({ + const response = await telemetryClient.search({ index: [indices.error, indices.metric, indices.transaction], body: { + track_total_hits: false, size: 0, timeout, query: { @@ -1000,8 +1015,8 @@ export const tasks: TelemetryTask[] = [ }, { name: 'indices_stats', - executor: async ({ indicesStats, indices }) => { - const response = await indicesStats({ + executor: async ({ indices, telemetryClient }) => { + const response = await telemetryClient.indicesStats({ index: [ indices.apmAgentConfigurationIndex, indices.error, @@ -1013,8 +1028,50 @@ export const tasks: TelemetryTask[] = [ ], }); + const metricIndicesResponse = await telemetryClient.indicesStats({ + index: [indices.metric], + }); + + const tracesIndicesResponse = await telemetryClient.indicesStats({ + index: [indices.span, indices.transaction], + }); + return { indices: { + metric: { + shards: { + total: metricIndicesResponse._shards?.total ?? 0, + }, + all: { + total: { + docs: { + count: metricIndicesResponse._all?.total?.docs?.count ?? 0, + }, + store: { + size_in_bytes: + metricIndicesResponse._all?.total?.store?.size_in_bytes ?? + 0, + }, + }, + }, + }, + traces: { + shards: { + total: tracesIndicesResponse._shards?.total ?? 0, + }, + all: { + total: { + docs: { + count: tracesIndicesResponse._all?.total?.docs?.count ?? 0, + }, + store: { + size_in_bytes: + tracesIndicesResponse._all?.total?.store?.size_in_bytes ?? + 0, + }, + }, + }, + }, shards: { total: response._shards?.total ?? 0, }, @@ -1034,10 +1091,11 @@ export const tasks: TelemetryTask[] = [ }, { name: 'cardinality', - executor: async ({ indices, search }) => { - const allAgentsCardinalityResponse = await search({ + executor: async ({ indices, telemetryClient }) => { + const allAgentsCardinalityResponse = await telemetryClient.search({ index: [indices.transaction], body: { + track_total_hits: false, size: 0, timeout, query: { @@ -1060,9 +1118,10 @@ export const tasks: TelemetryTask[] = [ }, }); - const rumAgentCardinalityResponse = await search({ + const rumAgentCardinalityResponse = await telemetryClient.search({ index: [indices.transaction], body: { + track_total_hits: false, size: 0, timeout, query: { @@ -1161,10 +1220,11 @@ export const tasks: TelemetryTask[] = [ }, { name: 'per_service', - executor: async ({ indices, search }) => { - const response = await search({ + executor: async ({ indices, telemetryClient }) => { + const response = await telemetryClient.search({ index: [indices.transaction], body: { + track_total_hits: false, size: 0, timeout, query: { @@ -1176,18 +1236,28 @@ export const tasks: TelemetryTask[] = [ }, }, aggs: { - environments: { + service_names: { terms: { - field: SERVICE_ENVIRONMENT, - size: 1000, + field: SERVICE_NAME, + size: 2500, }, aggs: { - service_names: { + environments: { terms: { - field: SERVICE_NAME, - size: 1000, + field: SERVICE_ENVIRONMENT, + size: 5, }, aggs: { + instances: { + cardinality: { + field: SERVICE_NODE_NAME, + }, + }, + transaction_types: { + cardinality: { + field: TRANSACTION_TYPE, + }, + }, top_metrics: { top_metrics: { sort: '_score', @@ -1256,87 +1326,85 @@ export const tasks: TelemetryTask[] = [ }, }, }); - const envBuckets = response.aggregations?.environments.buckets ?? []; - const data: APMPerService[] = envBuckets.flatMap((envBucket) => { + const serviceBuckets = response.aggregations?.service_names.buckets ?? []; + const data: APMPerService[] = serviceBuckets.flatMap((serviceBucket) => { const envHash = createHash('sha256') - .update(envBucket.key as string) + .update(serviceBucket.key as string) .digest('hex'); - const serviceBuckets = envBucket.service_names?.buckets ?? []; - return serviceBuckets.map((serviceBucket) => { + const envBuckets = serviceBucket.environments?.buckets ?? []; + return envBuckets.map((envBucket) => { const nameHash = createHash('sha256') - .update(serviceBucket.key as string) + .update(envBucket.key as string) .digest('hex'); const fullServiceName = `${nameHash}~${envHash}`; return { service_id: fullServiceName, timed_out: response.timed_out, + num_service_nodes: envBucket.instances.value ?? 1, + num_transaction_types: envBucket.transaction_types.value ?? 0, cloud: { availability_zones: - serviceBucket[CLOUD_AVAILABILITY_ZONE]?.buckets.map( + envBucket[CLOUD_AVAILABILITY_ZONE]?.buckets.map( (inner) => inner.key as string ) ?? [], regions: - serviceBucket[CLOUD_REGION]?.buckets.map( + envBucket[CLOUD_REGION]?.buckets.map( (inner) => inner.key as string ) ?? [], providers: - serviceBucket[CLOUD_PROVIDER]?.buckets.map( + envBucket[CLOUD_PROVIDER]?.buckets.map( (inner) => inner.key as string ) ?? [], }, faas: { trigger: { type: - serviceBucket[FAAS_TRIGGER_TYPE]?.buckets.map( + envBucket[FAAS_TRIGGER_TYPE]?.buckets.map( (inner) => inner.key as string ) ?? [], }, }, agent: { - name: serviceBucket.top_metrics?.top[0].metrics[ - AGENT_NAME - ] as string, - version: serviceBucket.top_metrics?.top[0].metrics[ + name: envBucket.top_metrics?.top[0].metrics[AGENT_NAME] as string, + version: envBucket.top_metrics?.top[0].metrics[ AGENT_VERSION ] as string, }, service: { language: { - name: serviceBucket.top_metrics?.top[0].metrics[ + name: envBucket.top_metrics?.top[0].metrics[ SERVICE_LANGUAGE_NAME ] as string, - version: serviceBucket.top_metrics?.top[0].metrics[ + version: envBucket.top_metrics?.top[0].metrics[ SERVICE_LANGUAGE_VERSION ] as string, }, framework: { - name: serviceBucket.top_metrics?.top[0].metrics[ + name: envBucket.top_metrics?.top[0].metrics[ SERVICE_FRAMEWORK_NAME ] as string, - version: serviceBucket.top_metrics?.top[0].metrics[ + version: envBucket.top_metrics?.top[0].metrics[ SERVICE_FRAMEWORK_VERSION ] as string, }, runtime: { - name: serviceBucket.top_metrics?.top[0].metrics[ + name: envBucket.top_metrics?.top[0].metrics[ SERVICE_RUNTIME_NAME ] as string, - version: serviceBucket.top_metrics?.top[0].metrics[ + version: envBucket.top_metrics?.top[0].metrics[ SERVICE_RUNTIME_VERSION ] as string, }, }, kubernetes: { pod: { - name: serviceBucket.top_metrics?.top[0].metrics[ + name: envBucket.top_metrics?.top[0].metrics[ KUBERNETES_POD_NAME ] as string, }, }, container: { - id: serviceBucket.top_metrics?.top[0].metrics[ - CONTAINER_ID - ] as string, + id: envBucket.top_metrics?.top[0].metrics[CONTAINER_ID] as string, }, }; }); diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts index 659fbcaf634ac2..068d6429ec430c 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts @@ -4,10 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Observable, firstValueFrom } from 'rxjs'; + import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { CoreSetup, Logger, SavedObjectsErrorHelpers } from '@kbn/core/server'; -import { unwrapEsResponse } from '@kbn/observability-plugin/server'; import { TaskManagerSetupContract, TaskManagerStartContract, @@ -18,19 +17,17 @@ import { APM_TELEMETRY_SAVED_OBJECT_TYPE, } from '../../../common/apm_saved_object_constants'; import { getInternalSavedObjectsClient } from '../helpers/get_internal_saved_objects_client'; -import { getApmIndices } from '../../routes/settings/apm_indices/get_apm_indices'; -import { - collectDataTelemetry, - CollectTelemetryParams, -} from './collect_data_telemetry'; +import { collectDataTelemetry } from './collect_data_telemetry'; import { APMUsage } from './types'; import { apmSchema } from './schema'; +import { getApmIndices } from '../../routes/settings/apm_indices/get_apm_indices'; +import { getTelemetryClient } from './telemetry_client'; export const APM_TELEMETRY_TASK_NAME = 'apm-telemetry-task'; export async function createApmTelemetry({ core, - config$, + config, usageCollector, taskManager, logger, @@ -38,7 +35,7 @@ export async function createApmTelemetry({ isProd, }: { core: CoreSetup; - config$: Observable; + config: APMConfig; usageCollector: UsageCollectionSetup; taskManager: TaskManagerSetupContract; logger: Logger; @@ -60,40 +57,14 @@ export async function createApmTelemetry({ }); const savedObjectsClient = await getInternalSavedObjectsClient(core); + const indices = await getApmIndices({ config, savedObjectsClient }); + const telemetryClient = await getTelemetryClient({ core }); const collectAndStore = async () => { - const config = await firstValueFrom(config$); - const [{ elasticsearch }] = await core.getStartServices(); - const esClient = elasticsearch.client; - - const indices = await getApmIndices({ - config, - savedObjectsClient, - }); - - const search: CollectTelemetryParams['search'] = (params) => - unwrapEsResponse( - esClient.asInternalUser.search(params, { meta: true }) - ) as any; - - const indicesStats: CollectTelemetryParams['indicesStats'] = (params) => - unwrapEsResponse( - esClient.asInternalUser.indices.stats(params, { meta: true }) - ); - - const transportRequest: CollectTelemetryParams['transportRequest'] = ( - params - ) => - unwrapEsResponse( - esClient.asInternalUser.transport.request(params, { meta: true }) - ); - const dataTelemetry = await collectDataTelemetry({ - search, indices, + telemetryClient, logger, - indicesStats, - transportRequest, savedObjectsClient, isProd, }); diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts index 4430389785e1d6..c73eeae128cfe7 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/schema.ts @@ -118,6 +118,8 @@ const apmPerAgentSchema: Pick< export const apmPerServiceSchema: MakeSchemaFrom = { service_id: keyword, + num_service_nodes: long, + num_transaction_types: long, timed_out: { type: 'boolean' }, cloud: { availability_zones: { type: 'array', items: { type: 'keyword' } }, @@ -225,6 +227,24 @@ export const apmSchema: MakeSchemaFrom = { integrations: { ml: { all_jobs_count: long } }, indices: { + metric: { + shards: { total: long }, + all: { + total: { + docs: { count: long }, + store: { size_in_bytes: long }, + }, + }, + }, + traces: { + shards: { total: long }, + all: { + total: { + docs: { count: long }, + store: { size_in_bytes: long }, + }, + }, + }, shards: { total: long }, all: { total: { diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/telemetry_client.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/telemetry_client.ts new file mode 100644 index 00000000000000..b596166d91f2c4 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/telemetry_client.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 { estypes } from '@elastic/elasticsearch'; +import { CoreSetup } from '@kbn/core/server'; +import { ESSearchRequest, ESSearchResponse } from '@kbn/es-types'; +import { unwrapEsResponse } from '@kbn/observability-plugin/server'; + +interface RequiredSearchParams { + index: string | string[]; + body: { size: number; track_total_hits: boolean | number; timeout: string }; +} + +export interface TelemetryClient { + search( + params: TSearchRequest + ): Promise>; + + indicesStats( + params: estypes.IndicesStatsRequest + // promise returned by client has an abort property + // so we cannot use its ReturnType + ): Promise<{ + _all?: { + total?: { store?: { size_in_bytes?: number }; docs?: { count?: number } }; + }; + _shards?: { + total?: number; + }; + }>; + + transportRequest: (params: { + path: string; + method: 'get'; + }) => Promise; +} + +export async function getTelemetryClient({ + core, +}: { + core: CoreSetup; +}): Promise { + const [{ elasticsearch }] = await core.getStartServices(); + const esClient = elasticsearch.client; + + return { + search: (params) => + unwrapEsResponse( + esClient.asInternalUser.search(params, { meta: true }) + ) as any, + indicesStats: (params) => + unwrapEsResponse( + esClient.asInternalUser.indices.stats(params, { meta: true }) + ), + transportRequest: (params) => + unwrapEsResponse( + esClient.asInternalUser.transport.request(params, { meta: true }) + ), + }; +} diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts index ceadcbfc1ded24..518bb969bcb0eb 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/types.ts @@ -27,6 +27,8 @@ export interface AggregatedTransactionsCounts { export interface APMPerService { service_id: string; timed_out: boolean; + num_service_nodes: number; + num_transaction_types: number; cloud: { availability_zones: string[]; regions: string[]; @@ -157,6 +159,36 @@ export interface APMUsage { } >; indices: { + traces: { + shards: { + total: number; + }; + all: { + total: { + docs: { + count: number; + }; + store: { + size_in_bytes: number; + }; + }; + }; + }; + metric: { + shards: { + total: number; + }; + all: { + total: { + docs: { + count: number; + }; + store: { + size_in_bytes: number; + }; + }; + }; + }; shards: { total: number; }; diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 698abdfb14a9a5..f40ce41fa3f635 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -92,7 +92,7 @@ export class APMPlugin ) { createApmTelemetry({ core, - config$, + config: currentConfig, usageCollector: plugins.usageCollection, taskManager: plugins.taskManager, logger: this.logger, 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 deleted file mode 100644 index ff1019778ad56d..00000000000000 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_boolean_field_stats.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { - CommonCorrelationsQueryParams, - FieldValuePair, -} from '../../../../../common/correlations/types'; -import { BooleanFieldStats } from '../../../../../common/correlations/field_stats_types'; -import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; -import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; - -export const fetchBooleanFieldStats = async ({ - apmEventClient, - eventType, - start, - end, - environment, - kuery, - field, - query, -}: CommonCorrelationsQueryParams & { - apmEventClient: APMEventClient; - eventType: ProcessorEvent; - field: FieldValuePair; -}): Promise => { - const { fieldName } = field; - - const { aggregations } = await apmEventClient.search( - 'get_boolean_field_stats', - { - apm: { - events: [eventType], - }, - body: { - size: 0, - track_total_hits: false, - query: getCommonCorrelationsQuery({ - start, - end, - environment, - kuery, - query, - }), - aggs: { - sampled_value_count: { - filter: { exists: { field: fieldName } }, - }, - sampled_values: { - terms: { - field: fieldName, - size: 2, - }, - }, - }, - }, - } - ); - - const stats: BooleanFieldStats = { - fieldName: field.fieldName, - count: aggregations?.sampled_value_count.doc_count ?? 0, - }; - - const valueBuckets = aggregations?.sampled_values?.buckets ?? []; - valueBuckets.forEach((bucket) => { - stats[`${bucket.key.toString()}Count`] = bucket.doc_count; - }); - return stats; -}; 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 622c7b7d8952e2..c8d555f8ba25d1 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 @@ -6,6 +6,11 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + AggregationsAggregationContainer, + AggregationsSamplerAggregate, + AggregationsSingleBucketAggregateBase, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { CommonCorrelationsQueryParams, FieldValuePair, @@ -26,11 +31,43 @@ export const fetchFieldValueFieldStats = async ({ kuery, query, field, + samplerShardSize, }: CommonCorrelationsQueryParams & { eventType: ProcessorEvent; apmEventClient: APMEventClient; field: FieldValuePair; + samplerShardSize?: number; }): Promise => { + const shouldSample = samplerShardSize !== undefined && samplerShardSize > 0; + + let aggs: Record = { + filtered_count: { + filter: { + term: { + [`${field?.fieldName}`]: field?.fieldValue, + }, + }, + }, + }; + + if (shouldSample) { + aggs = { + sample: { + sampler: { + shard_size: samplerShardSize, + }, + aggs: { + filtered_count: { + filter: { + term: { + [`${field?.fieldName}`]: field?.fieldValue, + }, + }, + }, + }, + }, + }; + } const { aggregations } = await apmEventClient.search( 'get_field_value_field_stats', { @@ -47,30 +84,29 @@ export const fetchFieldValueFieldStats = async ({ kuery, query, }), - aggs: { - filtered_count: { - filter: { - term: { - [`${field?.fieldName}`]: field?.fieldValue, - }, - }, - }, - }, + aggs, }, } ); + const results = ( + shouldSample + ? (aggregations?.sample as AggregationsSamplerAggregate)?.filtered_count + : aggregations?.filtered_count + ) as AggregationsSingleBucketAggregateBase; + const topValues: TopValueBucket[] = [ { key: field.fieldValue, - doc_count: aggregations?.filtered_count.doc_count ?? 0, + doc_count: (results.doc_count as number) ?? 0, }, ]; const stats = { fieldName: field.fieldName, topValues, - topValuesSampleSize: aggregations?.filtered_count.doc_count ?? 0, + topValuesSampleSize: + (aggregations?.sample as AggregationsSamplerAggregate)?.doc_count ?? 0, }; return 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 deleted file mode 100644 index 90ed9b3a929507..00000000000000 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_fields_stats.ts +++ /dev/null @@ -1,138 +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 { chunk } from 'lodash'; -import { ES_FIELD_TYPES } from '@kbn/field-types'; -import { rangeQuery } from '@kbn/observability-plugin/server'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { - CommonCorrelationsQueryParams, - FieldValuePair, -} from '../../../../../common/correlations/types'; -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 { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; - -export const fetchFieldsStats = async ({ - apmEventClient, - eventType, - start, - end, - environment, - kuery, - query, - fieldsToSample, -}: CommonCorrelationsQueryParams & { - eventType: ProcessorEvent; - apmEventClient: APMEventClient; - fieldsToSample: string[]; -}): Promise<{ - stats: FieldStats[]; - errors: any[]; -}> => { - const stats: FieldStats[] = []; - const errors: any[] = []; - - if (fieldsToSample.length === 0) return { stats, errors }; - - const respMapping = await apmEventClient.fieldCaps( - 'get_field_caps_for_field_stats', - { - apm: { - events: [eventType], - }, - body: { - index_filter: { - bool: { - filter: [...rangeQuery(start, end)], - }, - }, - }, - fields: fieldsToSample, - } - ); - - const fieldStatsPromises = Object.entries(respMapping.fields) - .map(([key, value], idx) => { - const field: FieldValuePair = { fieldName: key, fieldValue: '' }; - const fieldTypes = Object.keys(value); - - for (const ft of fieldTypes) { - switch (ft) { - case ES_FIELD_TYPES.KEYWORD: - case ES_FIELD_TYPES.IP: - return fetchKeywordFieldStats({ - apmEventClient, - eventType, - start, - end, - environment, - kuery, - query, - field, - }); - break; - - case 'numeric': - case 'number': - case ES_FIELD_TYPES.FLOAT: - case ES_FIELD_TYPES.HALF_FLOAT: - case ES_FIELD_TYPES.SCALED_FLOAT: - case ES_FIELD_TYPES.DOUBLE: - case ES_FIELD_TYPES.INTEGER: - case ES_FIELD_TYPES.LONG: - case ES_FIELD_TYPES.SHORT: - case ES_FIELD_TYPES.UNSIGNED_LONG: - case ES_FIELD_TYPES.BYTE: - return fetchNumericFieldStats({ - apmEventClient, - eventType, - start, - end, - environment, - kuery, - query, - field, - }); - - break; - case ES_FIELD_TYPES.BOOLEAN: - return fetchBooleanFieldStats({ - apmEventClient, - eventType, - start, - end, - environment, - kuery, - query, - field, - }); - - default: - return; - } - } - }) - .filter((f) => f !== undefined) as Array>; - - const batches = chunk(fieldStatsPromises, 10); - for (let i = 0; i < batches.length; i++) { - try { - const results = await Promise.allSettled(batches[i]); - results.forEach((r) => { - if (r.status === 'fulfilled' && r.value !== undefined) { - stats.push(r.value); - } - }); - } catch (e) { - errors.push(e); - } - } - - return { stats, errors }; -}; 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 deleted file mode 100644 index 7127db07721e7f..00000000000000 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_keyword_field_stats.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 { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { - CommonCorrelationsQueryParams, - FieldValuePair, -} from '../../../../../common/correlations/types'; -import { KeywordFieldStats } from '../../../../../common/correlations/field_stats_types'; -import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; -import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; - -export const fetchKeywordFieldStats = async ({ - apmEventClient, - eventType, - start, - end, - environment, - kuery, - query, - field, -}: CommonCorrelationsQueryParams & { - apmEventClient: APMEventClient; - eventType: ProcessorEvent; - field: FieldValuePair; -}): Promise => { - const body = await apmEventClient.search('get_keyword_field_stats', { - apm: { - events: [eventType], - }, - body: { - size: 0, - track_total_hits: false, - query: getCommonCorrelationsQuery({ - start, - end, - kuery, - environment, - query, - }), - aggs: { - sampled_top: { - terms: { - field: field.fieldName, - size: 10, - }, - }, - }, - }, - }); - - const aggregations = body.aggregations; - const topValues = aggregations?.sampled_top?.buckets ?? []; - - const stats = { - fieldName: field.fieldName, - topValues, - topValuesSampleSize: topValues.reduce( - (acc, curr) => acc + curr.doc_count, - aggregations?.sampled_top?.sum_other_doc_count ?? 0 - ), - }; - - return stats; -}; 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 deleted file mode 100644 index 63bd2a3ea9c8b6..00000000000000 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_numeric_field_stats.ts +++ /dev/null @@ -1,95 +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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { - NumericFieldStats, - TopValueBucket, -} from '../../../../../common/correlations/field_stats_types'; -import { - CommonCorrelationsQueryParams, - FieldValuePair, -} from '../../../../../common/correlations/types'; -import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; -import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; - -export const fetchNumericFieldStats = async ({ - apmEventClient, - eventType, - start, - end, - environment, - kuery, - query, - field, -}: CommonCorrelationsQueryParams & { - apmEventClient: APMEventClient; - eventType: ProcessorEvent; - field: FieldValuePair; -}): Promise => { - const { fieldName } = field; - - const { aggregations } = await apmEventClient.search( - 'get_numeric_field_stats', - { - apm: { - events: [eventType], - }, - body: { - size: 0, - track_total_hits: false, - query: getCommonCorrelationsQuery({ - start, - end, - environment, - kuery, - query, - }), - aggs: { - sampled_field_stats: { - filter: { exists: { field: fieldName } }, - aggs: { - actual_stats: { - stats: { field: fieldName }, - }, - }, - }, - sampled_top: { - terms: { - field: fieldName, - size: 10, - order: { - _count: 'desc', - }, - }, - }, - }, - }, - } - ); - - const docCount = aggregations?.sampled_field_stats?.doc_count ?? 0; - const fieldStatsResp: Partial = - aggregations?.sampled_field_stats?.actual_stats ?? {}; - const topValues = aggregations?.sampled_top?.buckets ?? []; - - const stats: NumericFieldStats = { - fieldName: field.fieldName, - count: docCount, - min: fieldStatsResp?.min || 0, - max: fieldStatsResp?.max || 0, - avg: fieldStatsResp?.avg || 0, - topValues, - topValuesSampleSize: topValues.reduce( - (acc: number, curr: TopValueBucket) => acc + curr.doc_count, - aggregations?.sampled_top?.sum_other_doc_count ?? 0 - ), - }; - - return stats; -}; diff --git a/x-pack/plugins/apm/server/routes/correlations/route.ts b/x-pack/plugins/apm/server/routes/correlations/route.ts index cdadd7053f517a..00cba18950319e 100644 --- a/x-pack/plugins/apm/server/routes/correlations/route.ts +++ b/x-pack/plugins/apm/server/routes/correlations/route.ts @@ -26,7 +26,6 @@ import { import { fetchFieldValueFieldStats } from './queries/field_stats/fetch_field_value_field_stats'; 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'; @@ -91,74 +90,6 @@ const fieldCandidatesTransactionsRoute = createApmServerRoute({ }, }); -const fieldStatsTransactionsRoute = createApmServerRoute({ - endpoint: 'POST /internal/apm/correlations/field_stats/transactions', - params: t.type({ - body: t.intersection([ - t.partial({ - serviceName: t.string, - transactionName: t.string, - transactionType: t.string, - }), - t.type({ - fieldsToSample: t.array(t.string), - }), - environmentRt, - kueryRt, - rangeRt, - ]), - }), - options: { tags: ['access:apm'] }, - handler: async ( - resources - ): Promise<{ - stats: Array< - import('./../../../common/correlations/field_stats_types').FieldStats - >; - errors: any[]; - }> => { - const { context } = resources; - const { license } = await context.licensing; - if (!isActivePlatinumLicense(license)) { - throw Boom.forbidden(INVALID_LICENSE); - } - - const apmEventClient = await getApmEventClient(resources); - - const { - body: { - serviceName, - transactionName, - transactionType, - start, - end, - environment, - kuery, - fieldsToSample, - }, - } = resources.params; - - return fetchFieldsStats({ - apmEventClient, - eventType: ProcessorEvent.transaction, - start, - end, - environment, - kuery, - query: { - bool: { - filter: [ - ...termQuery(SERVICE_NAME, serviceName), - ...termQuery(TRANSACTION_TYPE, transactionType), - ...termQuery(TRANSACTION_NAME, transactionName), - ], - }, - }, - fieldsToSample, - }); - }, -}); - const fieldValueStatsTransactionsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/correlations/field_value_stats/transactions', params: t.type({ @@ -167,6 +98,7 @@ const fieldValueStatsTransactionsRoute = createApmServerRoute({ serviceName: t.string, transactionName: t.string, transactionType: t.string, + samplerShardSize: t.string, }), environmentRt, kueryRt, @@ -202,9 +134,13 @@ const fieldValueStatsTransactionsRoute = createApmServerRoute({ kuery, fieldName, fieldValue, + samplerShardSize: samplerShardSizeStr, }, } = resources.params; + const samplerShardSize = samplerShardSizeStr + ? parseInt(samplerShardSizeStr, 10) + : undefined; return fetchFieldValueFieldStats({ apmEventClient, eventType: ProcessorEvent.transaction, @@ -225,6 +161,7 @@ const fieldValueStatsTransactionsRoute = createApmServerRoute({ fieldName, fieldValue, }, + samplerShardSize, }); }, }); @@ -441,7 +378,6 @@ const pValuesTransactionsRoute = createApmServerRoute({ export const correlationsRouteRepository = { ...fieldCandidatesTransactionsRoute, - ...fieldStatsTransactionsRoute, ...fieldValueStatsTransactionsRoute, ...fieldValuePairsTransactionsRoute, ...significantCorrelationsTransactionsRoute, diff --git a/x-pack/plugins/cases/public/components/create/form_context.test.tsx b/x-pack/plugins/cases/public/components/create/form_context.test.tsx index 6ac37abddb5140..65a350679a8c59 100644 --- a/x-pack/plugins/cases/public/components/create/form_context.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.test.tsx @@ -6,8 +6,8 @@ */ import React from 'react'; -import type { RenderResult } from '@testing-library/react'; -import { act, waitFor, within } from '@testing-library/react'; +import type { Screen } from '@testing-library/react'; +import { act, waitFor, within, screen } from '@testing-library/react'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; import { CaseSeverity, CommentType, ConnectorTypes } from '../../../common/api'; @@ -48,8 +48,6 @@ import { waitForComponentToUpdate } from '../../common/test_utils'; import { userProfiles } from '../../containers/user_profiles/api.mock'; import { useLicense } from '../../common/use_license'; -const sampleId = 'case-id'; - jest.mock('../../containers/use_post_case'); jest.mock('../../containers/use_create_attachments'); jest.mock('../../containers/use_post_push_to_service'); @@ -82,6 +80,8 @@ const pushCaseToExternalService = jest.fn(); const useKibanaMock = useKibana as jest.Mocked; const useLicenseMock = useLicense as jest.Mock; +const sampleId = 'case-id'; + const defaultPostCase = { isLoading: false, isError: false, @@ -100,29 +100,45 @@ const defaultPostPushToService = { pushCaseToExternalService, }; -const fillFormReactTestingLib = async (renderResult: RenderResult) => { - const titleInput = within(renderResult.getByTestId('caseTitle')).getByTestId('input'); +const sampleDataWithoutTags = { + ...sampleData, + tags: [], +}; + +const fillFormReactTestingLib = async ({ + renderer, + withTags = false, +}: { + renderer: Screen; + withTags?: boolean; +}) => { + const titleInput = within(renderer.getByTestId('caseTitle')).getByTestId('input'); - userEvent.type(titleInput, sampleData.title); + userEvent.paste(titleInput, sampleDataWithoutTags.title); - const descriptionInput = renderResult.container.querySelector( - `[data-test-subj="caseDescription"] textarea` + const descriptionInput = within(renderer.getByTestId('caseDescription')).getByTestId( + 'euiMarkdownEditorTextArea' ); - if (descriptionInput) { - userEvent.type(descriptionInput, sampleData.description); - } + userEvent.paste(descriptionInput, sampleDataWithoutTags.description); - const caseTags = renderResult.getByTestId('caseTags'); + if (withTags) { + const caseTags = renderer.getByTestId('caseTags'); - for (let i = 0; i < sampleTags.length; i++) { - const tagsInput = await within(caseTags).findByTestId('comboBoxInput'); - userEvent.type(tagsInput, `${sampleTags[i]}{enter}`); + for (const tag of sampleTags) { + const tagsInput = await within(caseTags).findByTestId('comboBoxInput'); + userEvent.type(tagsInput, `${tag}{enter}`); + } } }; -// FLAKY: https://github.com/elastic/kibana/issues/142283 -describe.skip('Create case', () => { +const waitForFormToRender = async (renderer: Screen) => { + await waitFor(() => { + expect(renderer.getByTestId('caseTitle')).toBeTruthy(); + }); +}; + +describe('Create case', () => { const refetch = jest.fn(); const onFormSubmitSuccess = jest.fn(); const afterCaseCreated = jest.fn(); @@ -133,7 +149,7 @@ describe.skip('Create case', () => { beforeAll(() => { postCase.mockResolvedValue({ id: sampleId, - ...sampleData, + ...sampleDataWithoutTags, }); usePostCaseMock.mockImplementation(() => defaultPostCase); useCreateAttachmentsMock.mockImplementation(() => ({ createAttachments })); @@ -171,19 +187,22 @@ describe.skip('Create case', () => { describe('Step 1 - Case Fields', () => { it('renders correctly', async () => { - const renderResult = mockedContext.render( + mockedContext.render( ); - expect(renderResult.getByTestId('caseTitle')).toBeTruthy(); - expect(renderResult.getByTestId('caseSeverity')).toBeTruthy(); - expect(renderResult.getByTestId('caseDescription')).toBeTruthy(); - expect(renderResult.getByTestId('createCaseAssigneesComboBox')).toBeTruthy(); - expect(renderResult.getByTestId('caseTags')).toBeTruthy(); - expect(renderResult.getByTestId('caseConnectors')).toBeTruthy(); - expect(renderResult.getByTestId('case-creation-form-steps')).toBeTruthy(); + + await waitForFormToRender(screen); + + expect(screen.getByTestId('caseTitle')).toBeTruthy(); + expect(screen.getByTestId('caseSeverity')).toBeTruthy(); + expect(screen.getByTestId('caseDescription')).toBeTruthy(); + expect(screen.getByTestId('createCaseAssigneesComboBox')).toBeTruthy(); + expect(screen.getByTestId('caseTags')).toBeTruthy(); + expect(screen.getByTestId('caseConnectors')).toBeTruthy(); + expect(screen.getByTestId('case-creation-form-steps')).toBeTruthy(); }); it('should post case on submit click', async () => { @@ -192,17 +211,20 @@ describe.skip('Create case', () => { data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); - userEvent.click(renderResult.getByTestId('create-case-submit')); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen, withTags: true }); + + userEvent.click(screen.getByTestId('create-case-submit')); + await waitFor(() => { - expect(postCase).toBeCalledWith(sampleData); + expect(postCase).toBeCalledWith({ ...sampleDataWithoutTags, tags: sampleTags }); }); }); @@ -212,52 +234,52 @@ describe.skip('Create case', () => { data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - userEvent.click(renderResult.getByTestId('case-severity-selection')); + userEvent.click(screen.getByTestId('case-severity-selection')); await waitForEuiPopoverOpen(); - expect(renderResult.getByTestId('case-severity-selection-high')).toBeVisible(); - userEvent.click(renderResult.getByTestId('case-severity-selection-high')); - userEvent.click(renderResult.getByTestId('create-case-submit')); + expect(screen.getByTestId('case-severity-selection-high')).toBeVisible(); + + userEvent.click(screen.getByTestId('case-severity-selection-high')); + userEvent.click(screen.getByTestId('create-case-submit')); + await waitFor(() => { expect(postCase).toBeCalledWith({ - ...sampleData, + ...sampleDataWithoutTags, severity: CaseSeverity.HIGH, }); }); }); - it('does not submits the title when the length is longer than 64 characters', async () => { - const longTitle = - 'This is a title that should not be saved as it is longer than 64 characters.{enter}'; + it('does not submits the title when the length is longer than 160 characters', async () => { + const longTitle = 'a'.repeat(161); - const renderResult = mockedContext.render( + mockedContext.render( ); - await act(async () => { - const titleInput = within(renderResult.getByTestId('caseTitle')).getByTestId('input'); - await userEvent.type(titleInput, longTitle, { delay: 1 }); - }); + await waitForFormToRender(screen); - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + const titleInput = within(screen.getByTestId('caseTitle')).getByTestId('input'); + userEvent.paste(titleInput, longTitle); + + userEvent.click(screen.getByTestId('create-case-submit')); await waitFor(() => { expect( - renderResult.getByText('The length of the title is too long. The maximum length is 64.') + screen.getByText('The length of the title is too long. The maximum length is 160.') ).toBeInTheDocument(); }); @@ -270,28 +292,26 @@ describe.skip('Create case', () => { data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - act(() => { - const syncAlertsButton = within(renderResult.getByTestId('caseSyncAlerts')).getByTestId( - 'input' - ); - userEvent.click(syncAlertsButton); - }); + const syncAlertsButton = within(screen.getByTestId('caseSyncAlerts')).getByTestId('input'); - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + userEvent.click(syncAlertsButton); + userEvent.click(screen.getByTestId('create-case-submit')); await waitFor(() => - expect(postCase).toBeCalledWith({ ...sampleData, settings: { syncAlerts: false } }) + expect(postCase).toBeCalledWith({ + ...sampleDataWithoutTags, + settings: { syncAlerts: false }, + }) ); }); @@ -299,38 +319,47 @@ describe.skip('Create case', () => { mockedContext = createAppMockRenderer({ features: { alerts: { sync: false, enabled: true } }, }); + useGetConnectorsMock.mockReturnValue({ ...sampleConnectorData, data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); + + userEvent.click(screen.getByTestId('create-case-submit')); await waitFor(() => - expect(postCase).toBeCalledWith({ ...sampleData, settings: { syncAlerts: false } }) + expect(postCase).toBeCalledWith({ + ...sampleDataWithoutTags, + settings: { syncAlerts: false }, + }) ); }); it('should select LOW as the default severity', async () => { - const renderResult = mockedContext.render( + mockedContext.render( ); - expect(renderResult.getByTestId('caseSeverity')).toBeTruthy(); + + await waitForFormToRender(screen); + + expect(screen.getByTestId('caseSeverity')).toBeTruthy(); // there should be 2 low elements. one for the options popover and one for the displayed one. - expect(renderResult.getAllByTestId('case-severity-selection-low').length).toBe(2); + expect(screen.getAllByTestId('case-severity-selection-low').length).toBe(2); + + await waitForComponentToUpdate(); }); it('should select the default connector set in the configuration', async () => { @@ -350,21 +379,21 @@ describe.skip('Create case', () => { data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); + + userEvent.click(screen.getByTestId('create-case-submit')); await waitFor(() => expect(postCase).toBeCalledWith({ - ...sampleData, + ...sampleDataWithoutTags, connector: { fields: { impact: null, @@ -398,19 +427,20 @@ describe.skip('Create case', () => { data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); + + userEvent.click(screen.getByTestId('create-case-submit')); + await waitFor(() => { - expect(postCase).toBeCalledWith(sampleData); + expect(postCase).toBeCalledWith(sampleDataWithoutTags); expect(pushCaseToExternalService).not.toHaveBeenCalled(); }); }); @@ -423,47 +453,38 @@ describe.skip('Create case', () => { data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connectors')); - }); + userEvent.click(screen.getByTestId('dropdown-connectors')); await waitFor(() => { - expect(renderResult.getByTestId('dropdown-connector-jira-1')).toBeInTheDocument(); + expect(screen.getByTestId('dropdown-connector-jira-1')).toBeInTheDocument(); }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connector-jira-1')); + userEvent.click(screen.getByTestId('dropdown-connector-jira-1'), undefined, { + skipPointerEventsCheck: true, }); await waitFor(() => { - expect(renderResult.getByTestId('issueTypeSelect')).toBeInTheDocument(); - expect(renderResult.getByTestId('prioritySelect')).toBeInTheDocument(); - }); - - act(() => { - userEvent.selectOptions(renderResult.getByTestId('issueTypeSelect'), ['10007']); - }); - - act(() => { - userEvent.selectOptions(renderResult.getByTestId('prioritySelect'), ['Low']); + expect(screen.getByTestId('issueTypeSelect')).toBeInTheDocument(); + expect(screen.getByTestId('prioritySelect')).toBeInTheDocument(); }); - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + userEvent.selectOptions(screen.getByTestId('issueTypeSelect'), ['10007']); + userEvent.selectOptions(screen.getByTestId('prioritySelect'), ['Low']); + userEvent.click(screen.getByTestId('create-case-submit')); await waitFor(() => { expect(postCase).toBeCalledWith({ - ...sampleData, + ...sampleDataWithoutTags, connector: { id: 'jira-1', name: 'Jira', @@ -471,6 +492,7 @@ describe.skip('Create case', () => { fields: { issueType: '10007', parent: null, priority: 'Low' }, }, }); + expect(pushCaseToExternalService).toHaveBeenCalledWith({ caseId: sampleId, connector: { @@ -480,9 +502,10 @@ describe.skip('Create case', () => { fields: { issueType: '10007', parent: null, priority: 'Low' }, }, }); + expect(onFormSubmitSuccess).toHaveBeenCalledWith({ id: sampleId, - ...sampleData, + ...sampleDataWithoutTags, }); }); }); @@ -493,53 +516,42 @@ describe.skip('Create case', () => { data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connectors')); - }); + userEvent.click(screen.getByTestId('dropdown-connectors')); await waitFor(() => { - expect(renderResult.getByTestId('dropdown-connector-resilient-2')).toBeInTheDocument(); + expect(screen.getByTestId('dropdown-connector-resilient-2')).toBeInTheDocument(); }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connector-resilient-2')); + userEvent.click(screen.getByTestId('dropdown-connector-resilient-2'), undefined, { + skipPointerEventsCheck: true, }); await waitFor(() => { - expect(renderResult.getByTestId('incidentTypeComboBox')).toBeInTheDocument(); - expect(renderResult.getByTestId('severitySelect')).toBeInTheDocument(); + expect(screen.getByTestId('incidentTypeComboBox')).toBeInTheDocument(); + expect(screen.getByTestId('severitySelect')).toBeInTheDocument(); }); - const checkbox = within(renderResult.getByTestId('incidentTypeComboBox')).getByTestId( + const checkbox = within(screen.getByTestId('incidentTypeComboBox')).getByTestId( 'comboBoxSearchInput' ); - await act(async () => { - await userEvent.type(checkbox, 'Denial of Service{enter}', { - delay: 2, - }); - }); - - act(() => { - userEvent.selectOptions(renderResult.getByTestId('severitySelect'), ['4']); - }); - - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + userEvent.type(checkbox, 'Denial of Service{enter}'); + userEvent.selectOptions(screen.getByTestId('severitySelect'), ['4']); + userEvent.click(screen.getByTestId('create-case-submit')); await waitFor(() => { expect(postCase).toBeCalledWith({ - ...sampleData, + ...sampleDataWithoutTags, connector: { id: 'resilient-2', name: 'My Connector 2', @@ -560,7 +572,7 @@ describe.skip('Create case', () => { expect(onFormSubmitSuccess).toHaveBeenCalledWith({ id: sampleId, - ...sampleData, + ...sampleDataWithoutTags, }); }); }); @@ -571,25 +583,24 @@ describe.skip('Create case', () => { data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connectors')); - }); + userEvent.click(screen.getByTestId('dropdown-connectors')); await waitFor(() => { - expect(renderResult.getByTestId('dropdown-connector-servicenow-1')).toBeInTheDocument(); + expect(screen.getByTestId('dropdown-connector-servicenow-1')).toBeInTheDocument(); }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connector-servicenow-1')); + userEvent.click(screen.getByTestId('dropdown-connector-servicenow-1'), undefined, { + skipPointerEventsCheck: true, }); await waitFor(() => { @@ -602,26 +613,16 @@ describe.skip('Create case', () => { }); ['severitySelect', 'urgencySelect', 'impactSelect'].forEach((subj) => { - act(() => { - userEvent.selectOptions(renderResult.getByTestId(subj), ['2']); - }); + userEvent.selectOptions(screen.getByTestId(subj), ['2']); }); - act(() => { - userEvent.selectOptions(renderResult.getByTestId('categorySelect'), ['software']); - }); - - act(() => { - userEvent.selectOptions(renderResult.getByTestId('subcategorySelect'), ['os']); - }); - - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + userEvent.selectOptions(screen.getByTestId('categorySelect'), ['software']); + userEvent.selectOptions(screen.getByTestId('subcategorySelect'), ['os']); + userEvent.click(screen.getByTestId('create-case-submit')); await waitFor(() => { expect(postCase).toBeCalledWith({ - ...sampleData, + ...sampleDataWithoutTags, connector: { id: 'servicenow-1', name: 'My Connector', @@ -654,7 +655,7 @@ describe.skip('Create case', () => { expect(onFormSubmitSuccess).toHaveBeenCalledWith({ id: sampleId, - ...sampleData, + ...sampleDataWithoutTags, }); }); }); @@ -665,25 +666,24 @@ describe.skip('Create case', () => { data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connectors')); - }); + userEvent.click(screen.getByTestId('dropdown-connectors')); await waitFor(() => { - expect(renderResult.getByTestId('dropdown-connector-servicenow-sir')).toBeInTheDocument(); + expect(screen.getByTestId('dropdown-connector-servicenow-sir')).toBeInTheDocument(); }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connector-servicenow-sir')); + userEvent.click(screen.getByTestId('dropdown-connector-servicenow-sir'), undefined, { + skipPointerEventsCheck: true, }); await waitFor(() => { @@ -695,29 +695,15 @@ describe.skip('Create case', () => { onChoicesSuccess(useGetChoicesResponse.choices); }); - act(() => { - userEvent.click(renderResult.getByTestId('destIpCheckbox')); - }); - - act(() => { - userEvent.selectOptions(renderResult.getByTestId('prioritySelect'), ['1']); - }); - - act(() => { - userEvent.selectOptions(renderResult.getByTestId('categorySelect'), ['Denial of Service']); - }); - - act(() => { - userEvent.selectOptions(renderResult.getByTestId('subcategorySelect'), ['26']); - }); - - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + userEvent.click(screen.getByTestId('destIpCheckbox')); + userEvent.selectOptions(screen.getByTestId('prioritySelect'), ['1']); + userEvent.selectOptions(screen.getByTestId('categorySelect'), ['Denial of Service']); + userEvent.selectOptions(screen.getByTestId('subcategorySelect'), ['26']); + userEvent.click(screen.getByTestId('create-case-submit')); await waitFor(() => { expect(postCase).toBeCalledWith({ - ...sampleData, + ...sampleDataWithoutTags, connector: { id: 'servicenow-sir', name: 'My Connector SIR', @@ -754,7 +740,7 @@ describe.skip('Create case', () => { expect(onFormSubmitSuccess).toHaveBeenCalledWith({ id: sampleId, - ...sampleData, + ...sampleDataWithoutTags, }); }); }); @@ -766,36 +752,32 @@ describe.skip('Create case', () => { data: connectorsMock, }); - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connectors')); - }); + userEvent.click(screen.getByTestId('dropdown-connectors')); await waitFor(() => { - expect(renderResult.getByTestId('dropdown-connector-jira-1')).toBeInTheDocument(); + expect(screen.getByTestId('dropdown-connector-jira-1')).toBeInTheDocument(); }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connector-jira-1')); - }); - - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); + userEvent.click(screen.getByTestId('dropdown-connector-jira-1'), undefined, { + skipPointerEventsCheck: true, }); + userEvent.click(screen.getByTestId('create-case-submit')); await waitFor(() => { expect(afterCaseCreated).toHaveBeenCalledWith( { id: sampleId, - ...sampleData, + ...sampleDataWithoutTags, }, createAttachments ); @@ -830,18 +812,17 @@ describe.skip('Create case', () => { }, ]; - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + userEvent.click(screen.getByTestId('create-case-submit')); await waitForComponentToUpdate(); @@ -860,18 +841,17 @@ describe.skip('Create case', () => { }); const attachments: CaseAttachments = []; - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + userEvent.click(screen.getByTestId('create-case-submit')); await waitForComponentToUpdate(); @@ -896,7 +876,7 @@ describe.skip('Create case', () => { }, ]; - const renderResult = mockedContext.render( + mockedContext.render( { ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connectors')); - }); + userEvent.click(screen.getByTestId('dropdown-connectors')); await waitFor(() => { - expect(renderResult.getByTestId('dropdown-connector-jira-1')).toBeInTheDocument(); + expect(screen.getByTestId('dropdown-connector-jira-1')).toBeInTheDocument(); }); - act(() => { - userEvent.click(renderResult.getByTestId('dropdown-connector-jira-1')); + userEvent.click(screen.getByTestId('dropdown-connector-jira-1'), undefined, { + skipPointerEventsCheck: true, }); - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + userEvent.click(screen.getByTestId('create-case-submit')); await waitFor(() => { expect(postCase).toHaveBeenCalled(); @@ -954,68 +931,56 @@ describe.skip('Create case', () => { actions: { save: false, show: false }, }; - const result = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(result); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - await act(async () => { - userEvent.click(result.getByTestId('create-case-submit')); - }); + userEvent.click(screen.getByTestId('create-case-submit')); + await waitForComponentToUpdate(); expect(pushCaseToExternalService).not.toHaveBeenCalled(); }); }); describe('Assignees', () => { it('should submit assignees', async () => { - const renderResult = mockedContext.render( + mockedContext.render( ); - await fillFormReactTestingLib(renderResult); + await waitForFormToRender(screen); + await fillFormReactTestingLib({ renderer: screen }); - const assigneesComboBox = within(renderResult.getByTestId('createCaseAssigneesComboBox')); + const assigneesComboBox = within(screen.getByTestId('createCaseAssigneesComboBox')); await waitFor(() => { expect(assigneesComboBox.getByTestId('comboBoxSearchInput')).not.toBeDisabled(); }); - await act(async () => { - await userEvent.type(assigneesComboBox.getByTestId('comboBoxSearchInput'), 'dr', { - delay: 1, - }); - }); + userEvent.paste(assigneesComboBox.getByTestId('comboBoxSearchInput'), 'dr'); await waitFor(() => { expect( - renderResult.getByTestId('comboBoxOptionsList createCaseAssigneesComboBox-optionsList') + screen.getByTestId('comboBoxOptionsList createCaseAssigneesComboBox-optionsList') ).toBeInTheDocument(); }); - await waitFor(async () => { - expect(renderResult.getByText(`${userProfiles[0].user.full_name}`)).toBeInTheDocument(); - }); - - act(() => { - userEvent.click(renderResult.getByText(`${userProfiles[0].user.full_name}`)); - }); - - act(() => { - userEvent.click(renderResult.getByTestId('create-case-submit')); - }); + userEvent.click(await screen.findByText(`${userProfiles[0].user.full_name}`)); + userEvent.click(screen.getByTestId('create-case-submit')); await waitForComponentToUpdate(); expect(postCase).toBeCalledWith({ - ...sampleData, + ...sampleDataWithoutTags, assignees: [{ uid: userProfiles[0].uid }], }); }); @@ -1023,14 +988,16 @@ describe.skip('Create case', () => { it('should not render the assignees on basic license', async () => { useLicenseMock.mockReturnValue({ isAtLeastPlatinum: () => false }); - const renderResult = mockedContext.render( + mockedContext.render( ); - expect(renderResult.queryByTestId('createCaseAssigneesComboBox')).toBeNull(); + await waitForFormToRender(screen); + await waitForComponentToUpdate(); + expect(screen.queryByTestId('createCaseAssigneesComboBox')).toBeNull(); }); }); }); diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 052f3cb743d37a..a78266a51f2790 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -8,7 +8,6 @@ export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status'; export const STATS_ROUTE_PATH = '/internal/cloud_security_posture/stats'; export const BENCHMARKS_ROUTE_PATH = '/internal/cloud_security_posture/benchmarks'; -export const ES_PIT_ROUTE_PATH = '/internal/cloud_security_posture/es_pit'; export const CLOUD_SECURITY_POSTURE_PACKAGE_NAME = 'cloud_security_posture'; @@ -47,11 +46,3 @@ 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/common/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/constants.ts index 6356f00b5ec9f1..15f1e890cd6720 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts @@ -15,6 +15,12 @@ export const statusColors = { }; export const CSP_MOMENT_FORMAT = 'MMMM D, YYYY @ HH:mm:ss.SSS'; +export const MAX_FINDINGS_TO_LOAD = 500; +export const DEFAULT_VISIBLE_ROWS_PER_PAGE = 25; + +export const LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY = 'cloudPosture:findings:pageSize'; +export const LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY = 'cloudPosture:benchmark:pageSize'; +export const LOCAL_STORAGE_PAGE_SIZE_RULES_KEY = 'cloudPosture:rules:pageSize'; export type CloudPostureIntegrations = typeof cloudPostureIntegrations; diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_size.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_size.ts new file mode 100644 index 00000000000000..314dfbe661d93f --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_size.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 useLocalStorage from 'react-use/lib/useLocalStorage'; +import { DEFAULT_VISIBLE_ROWS_PER_PAGE } from '../constants'; + +/** + * @description handles persisting the users table row size selection + */ +export const usePageSize = (localStorageKey: string) => { + const [persistedPageSize, setPersistedPageSize] = useLocalStorage( + localStorageKey, + DEFAULT_VISIBLE_ROWS_PER_PAGE + ); + + let pageSize: number = DEFAULT_VISIBLE_ROWS_PER_PAGE; + + if (persistedPageSize) { + pageSize = persistedPageSize; + } + + return { pageSize, setPageSize: setPersistedPageSize }; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts new file mode 100644 index 00000000000000..e089724b25909d --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.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 { useMemo } from 'react'; + +/** + * @description given an array index and page size, returns a slice of said array. + */ +export const usePageSlice = (data: any[] | undefined, pageIndex: number, pageSize: number) => { + return useMemo(() => { + if (!data) { + return []; + } + + const cursor = pageIndex * pageSize; + return data.slice(cursor, cursor + pageSize); + }, [data, pageIndex, pageSize]); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/navigation/use_navigate_to_cis_integration.ts b/x-pack/plugins/cloud_security_posture/public/common/navigation/use_navigate_to_cis_integration.ts index 76d85983369ddc..a19be2f8fd6298 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/navigation/use_navigate_to_cis_integration.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/navigation/use_navigate_to_cis_integration.ts @@ -6,6 +6,7 @@ */ import { pagePathGetters, pkgKeyFromPackageInfo } from '@kbn/fleet-plugin/public'; +import { CLOUD_SECURITY_POSTURE_PACKAGE_NAME } from '../../../common/constants'; import { useCisKubernetesIntegration } from '../api/use_cis_kubernetes_integration'; import { useKibana } from '../hooks/use_kibana'; @@ -16,7 +17,8 @@ export const useCISIntegrationLink = (): string | undefined => { if (!cisIntegration.isSuccess) return; const path = pagePathGetters - .integration_details_overview({ + .add_integration_to_policy({ + integration: CLOUD_SECURITY_POSTURE_PACKAGE_NAME, pkgkey: pkgKeyFromPackageInfo({ name: cisIntegration.data.item.name, version: cisIntegration.data.item.version, diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx index 12b26d38ac7c01..89fc1c14a93e80 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx @@ -77,7 +77,7 @@ const Indexing = () => (

} 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 ce615733e4b989..29bc94dd739ece 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,7 +6,6 @@ */ import React, { useState } from 'react'; -import useLocalStorage from 'react-use/lib/useLocalStorage'; import { EuiFieldSearch, EuiFieldSearchProps, @@ -31,7 +30,8 @@ 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'; +import { LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY } from '../../common/constants'; +import { usePageSize } from '../../common/hooks/use_page_size'; const SEARCH_DEBOUNCE_MS = 300; @@ -128,14 +128,11 @@ const BenchmarkSearchField = ({ }; export const Benchmarks = () => { - const [pageSize, setPageSize] = useLocalStorage( - LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY, - 10 - ); + const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY); const [query, setQuery] = useState({ name: '', page: 1, - perPage: pageSize || 10, + perPage: pageSize, sortField: 'package_policy.name', sortOrder: 'asc', }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/es_pit/findings_es_pit_context.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/es_pit/findings_es_pit_context.ts deleted file mode 100644 index aa1d660229d756..00000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/es_pit/findings_es_pit_context.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { createContext, type MutableRefObject } from 'react'; -import type { UseQueryResult } from '@tanstack/react-query'; - -interface FindingsEsPitContextValue { - setPitId(newPitId: string): void; - pitIdRef: MutableRefObject; - pitQuery: UseQueryResult; -} - -// Default value should never be used, it can not be instantiated statically. Always wrap in a provider with a value -export const FindingsEsPitContext = createContext( - {} as FindingsEsPitContextValue -); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/es_pit/use_findings_es_pit.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/es_pit/use_findings_es_pit.ts deleted file mode 100644 index d8f2b22f501d4f..00000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/es_pit/use_findings_es_pit.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useCallback, useRef, useState } from 'react'; -import { useQuery } from '@tanstack/react-query'; -import { CSP_LATEST_FINDINGS_DATA_VIEW, ES_PIT_ROUTE_PATH } from '../../../../common/constants'; -import { useKibana } from '../../../common/hooks/use_kibana'; -import { FINDINGS_PIT_KEEP_ALIVE } from '../constants'; - -export const useFindingsEsPit = (queryKey: string) => { - // Using a reference for the PIT ID to avoid re-rendering when it changes - const pitIdRef = useRef(); - // Using this state as an internal control to ensure we run the query to open the PIT once and only once - const [isPitIdSet, setPitIdSet] = useState(false); - const setPitId = useCallback( - (newPitId: string) => { - pitIdRef.current = newPitId; - setPitIdSet(true); - }, - [pitIdRef, setPitIdSet] - ); - - const { http } = useKibana().services; - const pitQuery = useQuery( - ['findingsPitQuery', queryKey], - () => - http.post(ES_PIT_ROUTE_PATH, { - query: { index_name: CSP_LATEST_FINDINGS_DATA_VIEW, keep_alive: FINDINGS_PIT_KEEP_ALIVE }, - }), - { - enabled: !isPitIdSet, - onSuccess: (pitId) => { - setPitId(pitId); - }, - cacheTime: 0, - } - ); - - return { pitIdRef, setPitId, pitQuery }; -}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.test.tsx index 8e330abc93539b..d0744239a975b8 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.test.tsx @@ -26,7 +26,6 @@ import { useCISIntegrationPoliciesLink } from '../../common/navigation/use_navig import { useCISIntegrationLink } from '../../common/navigation/use_navigate_to_cis_integration'; import { NO_FINDINGS_STATUS_TEST_SUBJ } from '../../components/test_subjects'; import { render } from '@testing-library/react'; -import { useFindingsEsPit } from './es_pit/use_findings_es_pit'; import { expectIdsInDoc } from '../../test/utils'; import { fleetMock } from '@kbn/fleet-plugin/public/mocks'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; @@ -36,19 +35,10 @@ jest.mock('../../common/api/use_setup_status_api'); jest.mock('../../common/hooks/use_subscription_status'); jest.mock('../../common/navigation/use_navigate_to_cis_integration_policies'); jest.mock('../../common/navigation/use_navigate_to_cis_integration'); -jest.mock('./es_pit/use_findings_es_pit'); const chance = new Chance(); beforeEach(() => { jest.restoreAllMocks(); - (useFindingsEsPit as jest.Mock).mockImplementation(() => ({ - pitQuery: createReactQueryResponse({ - status: 'success', - data: [], - }), - setPitId: () => {}, - pitIdRef: chance.guid(), - })); (useSubscriptionStatus as jest.Mock).mockImplementation(() => createReactQueryResponse({ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx index e7aeed2e3e837f..8830724193e1c8 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings.tsx @@ -5,14 +5,11 @@ * 2.0. */ import React from 'react'; -import type { UseQueryResult } from '@tanstack/react-query'; import { Redirect, Switch, Route, useLocation } from 'react-router-dom'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api'; import { NoFindingsStates } from '../../components/no_findings_states'; import { CloudPosturePage } from '../../components/cloud_posture_page'; -import { useFindingsEsPit } from './es_pit/use_findings_es_pit'; -import { FindingsEsPitContext } from './es_pit/findings_es_pit_context'; import { useLatestFindingsDataView } from '../../common/api/use_latest_findings_data_view'; import { cloudPosturePages, findingsNavigation } from '../../common/navigation/constants'; import { FindingsByResourceContainer } from './latest_findings_by_resource/findings_by_resource_container'; @@ -21,59 +18,43 @@ import { LatestFindingsContainer } from './latest_findings/latest_findings_conta export const Findings = () => { const location = useLocation(); const dataViewQuery = useLatestFindingsDataView(); - // TODO: Consider splitting the PIT window so that each "group by" view has its own PIT - const { pitQuery, pitIdRef, setPitId } = useFindingsEsPit('findings'); const getSetupStatus = useCspSetupStatusApi(); const hasFindings = getSetupStatus.data?.status === 'indexed'; if (!hasFindings) return ; - let queryForCloudPosturePage: UseQueryResult = dataViewQuery; - if (pitQuery.isError || pitQuery.isLoading) { - queryForCloudPosturePage = pitQuery; - } - return ( - - , - setPitId, - }} - > - - ( - - )} - /> - ( - - - - )} - /> - } - /> - } - /> - - + + + ( + + )} + /> + ( + + + + )} + /> + } + /> + } + /> + ); }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx index 583421f645c7a5..0b00136b165c5f 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx @@ -112,7 +112,7 @@ export const FindingsRuleFlyout = ({ onClose, findings }: FindingFlyoutProps) => const [tab, setTab] = useState(tabs[0]); return ( - + diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.test.tsx index 5f5ee2e5a7ecfe..2d709433e7fc5c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.test.tsx @@ -4,13 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { UseQueryResult } from '@tanstack/react-query'; -import { createReactQueryResponse } from '../../../test/fixtures/react_query'; import React from 'react'; import { render } from '@testing-library/react'; import { LatestFindingsContainer, getDefaultQuery } from './latest_findings_container'; import { createStubDataView } from '@kbn/data-views-plugin/common/mocks'; import { CSP_LATEST_FINDINGS_DATA_VIEW } from '../../../../common/constants'; +import { DEFAULT_VISIBLE_ROWS_PER_PAGE } from '../../../common/constants'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { TestProvider } from '../../../test/test_provider'; @@ -20,7 +19,6 @@ import { useLocation } from 'react-router-dom'; import { RisonObject } from 'rison-node'; import { buildEsQuery } from '@kbn/es-query'; import { getPaginationQuery } from '../utils/utils'; -import { FindingsEsPitContext } from '../es_pit/findings_es_pit_context'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks'; import { fleetMock } from '@kbn/fleet-plugin/public/mocks'; @@ -45,6 +43,7 @@ describe('', () => { filters: [], query: { language: 'kuery', query: '' }, }); + const pageSize = DEFAULT_VISIBLE_ROWS_PER_PAGE; const dataMock = dataPluginMock.createStartContract(); const dataView = createStubDataView({ spec: { @@ -56,13 +55,6 @@ describe('', () => { search: encodeQuery(query as unknown as RisonObject), }); - const setPitId = jest.fn(); - const pitIdRef = { current: '' }; - const pitQuery = createReactQueryResponse({ - status: 'success', - data: '', - }) as UseQueryResult; - render( ', () => { licensing: licensingMock.createStart(), }} > - - - + ); const baseQuery = { query: buildEsQuery(dataView, query.query, query.filters), - pitId: pitIdRef.current, }; expect(dataMock.search.search).toHaveBeenNthCalledWith(1, { params: getFindingsQuery({ ...baseQuery, - ...getPaginationQuery(query), + ...getPaginationQuery({ ...query, pageSize }), sort: query.sort, enabled: true, }), 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 92b6ca6dcc68eb..2aa13f19444ae1 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,7 +8,6 @@ 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'; @@ -22,7 +21,6 @@ import { FindingsDistributionBar } from '../layout/findings_distribution_bar'; import { getFindingsPageSizeInfo, getFilters, - getPaginationQuery, getPaginationTableParams, useBaseEsQuery, usePersistedQuery, @@ -30,9 +28,14 @@ import { import { PageTitle, PageTitleText } from '../layout/findings_layout'; import { FindingsGroupBySelector } from '../layout/findings_group_by_selector'; import { useUrlQuery } from '../../../common/hooks/use_url_query'; +import { usePageSlice } from '../../../common/hooks/use_page_slice'; +import { usePageSize } from '../../../common/hooks/use_page_size'; import { ErrorCallout } from '../layout/error_callout'; import { getLimitProperties } from '../utils/get_limit_properties'; -import { LOCAL_STORAGE_PAGE_SIZE_LATEST_FINDINGS_KEY } from '../../../../common/constants'; +import { + MAX_FINDINGS_TO_LOAD, + LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY, +} from '../../../common/constants'; export const getDefaultQuery = ({ query, @@ -42,18 +45,13 @@ export const getDefaultQuery = ({ filters, sort: { field: '@timestamp', direction: 'desc' }, pageIndex: 0, - pageSize: 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 - ); + const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY); + /** * Page URL query to ES query */ @@ -67,26 +65,24 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { * Page ES query result */ const findingsGroupByNone = useLatestFindings({ - ...getPaginationQuery({ - pageIndex: urlQuery.pageIndex, - pageSize: pageSize || urlQuery.pageSize, - }), query: baseEsQuery.query, sort: urlQuery.sort, enabled: !baseEsQuery.error, }); + const slicedPage = usePageSlice(findingsGroupByNone.data?.page, urlQuery.pageIndex, pageSize); + const error = findingsGroupByNone.error || baseEsQuery.error; const { isLastLimitedPage, limitedTotalItemCount } = useMemo( () => getLimitProperties( findingsGroupByNone.data?.total || 0, - MAX_ITEMS, - urlQuery.pageSize, + MAX_FINDINGS_TO_LOAD, + pageSize, urlQuery.pageIndex ), - [findingsGroupByNone.data?.total, urlQuery.pageIndex, urlQuery.pageSize] + [findingsGroupByNone.data?.total, urlQuery.pageIndex, pageSize] ); const handleDistributionClick = (evaluation: Evaluation) => { @@ -134,8 +130,8 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { failed: findingsGroupByNone.data.count.failed, ...getFindingsPageSizeInfo({ pageIndex: urlQuery.pageIndex, - pageSize: urlQuery.pageSize, - currentPageSize: findingsGroupByNone.data.page.length, + pageSize, + currentPageSize: slicedPage.length, }), }} /> @@ -143,9 +139,9 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { { setUrlQuery({ sort, pageIndex: page.index, - pageSize: page.size, }); }} onAddFilter={(field, value, negate) => @@ -182,7 +177,7 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { id="xpack.csp.findings.latestFindings.bottomBarLabel" defaultMessage="These are the first {maxItems} findings matching your search, refine your search to see others." values={{ - maxItems: MAX_ITEMS, + maxItems: MAX_FINDINGS_TO_LOAD, }} /> diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx index 78613d56a3fd1e..8ded88cd6507c0 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx @@ -8,6 +8,7 @@ import React, { useMemo, useState } from 'react'; import { EuiEmptyPrompt, EuiBasicTable, + useEuiTheme, type Pagination, type EuiBasicTableProps, type CriteriaWithPagination, @@ -24,6 +25,7 @@ import { getExpandColumn, type OnAddFilter, } from '../layout/findings_layout'; +import { getSelectedRowStyle } from '../utils/utils'; type TableProps = Required>; @@ -44,10 +46,12 @@ const FindingsTableComponent = ({ setTableOptions, onAddFilter, }: Props) => { + const { euiTheme } = useEuiTheme(); const [selectedFinding, setSelectedFinding] = useState(); const getRowProps = (row: CspFinding) => ({ 'data-test-subj': TEST_SUBJECTS.getFindingsTableRowTestId(row.resource.id), + style: getSelectedRowStyle(euiTheme, row, selectedFinding), }); const getCellProps = (row: CspFinding, column: EuiTableFieldDataColumnType) => ({ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/use_latest_findings.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/use_latest_findings.ts index 24028102536a62..b938c202014d86 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/use_latest_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/use_latest_findings.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useContext } from 'react'; import { useQuery } from '@tanstack/react-query'; import { number } from 'io-ts'; import { lastValueFrom } from 'rxjs'; @@ -14,24 +13,21 @@ import type { Pagination } from '@elastic/eui'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { i18n } from '@kbn/i18n'; import { CspFinding } from '../../../../common/schemas/csp_finding'; -import { FindingsEsPitContext } from '../es_pit/findings_es_pit_context'; import { extractErrorMessage } from '../../../../common/utils/helpers'; import type { Sort } from '../types'; import { useKibana } from '../../../common/hooks/use_kibana'; import type { FindingsBaseEsQuery } from '../types'; -import { FINDINGS_REFETCH_INTERVAL_MS } from '../constants'; import { getAggregationCount, getFindingsCountAggQuery } from '../utils/utils'; +import { CSP_LATEST_FINDINGS_DATA_VIEW } from '../../../../common/constants'; +import { MAX_FINDINGS_TO_LOAD } from '../../../common/constants'; interface UseFindingsOptions extends FindingsBaseEsQuery { - from: NonNullable['from']>; - size: NonNullable['size']>; sort: Sort; enabled: boolean; } export interface FindingsGroupByNoneQuery { pageIndex: Pagination['pageIndex']; - pageSize: Pagination['pageSize']; sort: Sort; } @@ -57,21 +53,14 @@ export const showErrorToast = ( else toasts.addDanger(extractErrorMessage(error, SEARCH_FAILED_TEXT)); }; -export const getFindingsQuery = ({ - query, - size, - from, - sort, - pitId, -}: UseFindingsOptions & { pitId: string }) => ({ +export const getFindingsQuery = ({ query, sort }: UseFindingsOptions) => ({ + index: CSP_LATEST_FINDINGS_DATA_VIEW, body: { query, sort: [{ [sort.field]: sort.direction }], - size, - from, + size: MAX_FINDINGS_TO_LOAD, aggs: getFindingsCountAggQuery(), }, - pit: { id: pitId }, ignore_unavailable: false, }); @@ -80,22 +69,17 @@ export const useLatestFindings = (options: UseFindingsOptions) => { data, notifications: { toasts }, } = useKibana().services; - const { pitIdRef, setPitId } = useContext(FindingsEsPitContext); - const params = { ...options, pitId: pitIdRef.current }; - return useQuery( - ['csp_findings', { params }], + ['csp_findings', { params: options }], async () => { const { - rawResponse: { hits, aggregations, pit_id: newPitId }, + rawResponse: { hits, aggregations }, } = await lastValueFrom( data.search.search({ - params: getFindingsQuery(params), + params: getFindingsQuery(options), }) ); - if (!aggregations) throw new Error('expected aggregations to be an defined'); - if (!Array.isArray(aggregations.count.buckets)) throw new Error('expected buckets to be an array'); @@ -103,19 +87,12 @@ export const useLatestFindings = (options: UseFindingsOptions) => { page: hits.hits.map((hit) => hit._source!), total: number.is(hits.total) ? hits.total : 0, count: getAggregationCount(aggregations.count.buckets), - newPitId: newPitId!, }; }, { enabled: options.enabled, keepPreviousData: true, onError: (err: Error) => showErrorToast(toasts, err), - onSuccess: ({ newPitId }) => { - setPitId(newPitId); - }, - // Refetching on an interval to ensure the PIT window stays open - refetchInterval: FINDINGS_REFETCH_INTERVAL_MS, - refetchIntervalInBackground: true, } ); }; 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 74bddc75109c41..1a373fc4174e05 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,20 +8,20 @@ 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 { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import type { Evaluation } from '../../../../common/types'; import { CloudPosturePageTitle } from '../../../components/cloud_posture_page_title'; import { FindingsSearchBar } from '../layout/findings_search_bar'; import * as TEST_SUBJECTS from '../test_subjects'; import { useUrlQuery } from '../../../common/hooks/use_url_query'; +import { usePageSlice } from '../../../common/hooks/use_page_slice'; +import { usePageSize } from '../../../common/hooks/use_page_size'; import type { FindingsBaseProps, FindingsBaseURLQuery } from '../types'; import { FindingsByResourceQuery, useFindingsByResource } from './use_findings_by_resource'; import { FindingsByResourceTable } from './findings_by_resource_table'; import { getFindingsPageSizeInfo, getFilters, - getPaginationQuery, getPaginationTableParams, useBaseEsQuery, usePersistedQuery, @@ -32,7 +32,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'; +import { LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY } from '../../../common/constants'; const getDefaultQuery = ({ query, @@ -41,7 +41,6 @@ const getDefaultQuery = ({ query, filters, pageIndex: 0, - pageSize: 10, sortDirection: 'desc', }); @@ -70,10 +69,7 @@ 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 - ); + const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY); /** * Page URL query to ES query @@ -88,10 +84,6 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { * Page ES query result */ const findingsGroupByResource = useFindingsByResource({ - ...getPaginationQuery({ - pageIndex: urlQuery.pageIndex, - pageSize: pageSize || urlQuery.pageSize, - }), sortDirection: urlQuery.sortDirection, query: baseEsQuery.query, enabled: !baseEsQuery.error, @@ -99,6 +91,8 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { const error = findingsGroupByResource.error || baseEsQuery.error; + const slicedPage = usePageSlice(findingsGroupByResource.data?.page, urlQuery.pageIndex, pageSize); + const handleDistributionClick = (evaluation: Evaluation) => { setUrlQuery({ pageIndex: 0, @@ -155,8 +149,8 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { failed: findingsGroupByResource.data.count.failed, ...getFindingsPageSizeInfo({ pageIndex: urlQuery.pageIndex, - pageSize: urlQuery.pageSize, - currentPageSize: findingsGroupByResource.data.page.length, + pageSize, + currentPageSize: slicedPage.length, }), }} /> @@ -164,9 +158,9 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { { setUrlQuery({ sortDirection: sort?.direction, pageIndex: page.index, - pageSize: page.size, }); }} sorting={{ 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 e61415b7d0af1f..f18738a0736fa9 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,7 +15,6 @@ 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'; @@ -25,11 +24,12 @@ import { PageTitle, PageTitleText } from '../../layout/findings_layout'; import { findingsNavigation } from '../../../../common/navigation/constants'; import { ResourceFindingsQuery, useResourceFindings } from './use_resource_findings'; import { useUrlQuery } from '../../../../common/hooks/use_url_query'; +import { usePageSlice } from '../../../../common/hooks/use_page_slice'; +import { usePageSize } from '../../../../common/hooks/use_page_size'; import type { FindingsBaseURLQuery, FindingsBaseProps } from '../../types'; import { getFindingsPageSizeInfo, getFilters, - getPaginationQuery, getPaginationTableParams, useBaseEsQuery, usePersistedQuery, @@ -38,7 +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'; +import { LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY } from '../../../../common/constants'; const getDefaultQuery = ({ query, @@ -48,7 +48,6 @@ const getDefaultQuery = ({ filters, sort: { field: 'result.evaluation' as keyof CspFinding, direction: 'asc' }, pageIndex: 0, - pageSize: 10, }); const BackToResourcesButton = () => ( @@ -92,10 +91,7 @@ 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 - ); + const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY); /** * Page URL query to ES query @@ -110,10 +106,6 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { * Page ES query result */ const resourceFindings = useResourceFindings({ - ...getPaginationQuery({ - pageSize: pageSize || urlQuery.pageSize, - pageIndex: urlQuery.pageIndex, - }), sort: urlQuery.sort, query: baseEsQuery.query, resourceId: params.resourceId, @@ -122,6 +114,8 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { const error = resourceFindings.error || baseEsQuery.error; + const slicedPage = usePageSlice(resourceFindings.data?.page, urlQuery.pageIndex, pageSize); + const handleDistributionClick = (evaluation: Evaluation) => { setUrlQuery({ pageIndex: 0, @@ -190,8 +184,8 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { failed: resourceFindings.data.count.failed, ...getFindingsPageSizeInfo({ pageIndex: urlQuery.pageIndex, - pageSize: urlQuery.pageSize, - currentPageSize: resourceFindings.data.page.length, + pageSize, + currentPageSize: slicedPage.length, }), }} /> @@ -199,9 +193,9 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { { }} setTableOptions={({ page, sort }) => { setPageSize(page.size); - setUrlQuery({ pageIndex: page.index, pageSize: page.size, sort }); + setUrlQuery({ pageIndex: page.index, sort }); }} onAddFilter={(field, value, negate) => setUrlQuery({ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.tsx index 976ddd8d8b37fe..8996410b9fb29b 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.tsx @@ -13,6 +13,7 @@ import { type EuiBasicTableColumn, type EuiTableActionsColumnType, type EuiBasicTableProps, + useEuiTheme, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { CspFinding } from '../../../../../common/schemas/csp_finding'; @@ -23,6 +24,7 @@ import { type OnAddFilter, } from '../../layout/findings_layout'; import { FindingsRuleFlyout } from '../../findings_flyout/findings_flyout'; +import { getSelectedRowStyle } from '../../utils/utils'; interface Props { items: CspFinding[]; @@ -41,8 +43,13 @@ const ResourceFindingsTableComponent = ({ setTableOptions, onAddFilter, }: Props) => { + const { euiTheme } = useEuiTheme(); const [selectedFinding, setSelectedFinding] = useState(); + const getRowProps = (row: CspFinding) => ({ + style: getSelectedRowStyle(euiTheme, row, selectedFinding), + }); + const columns: [ EuiTableActionsColumnType, ...Array> @@ -83,6 +90,7 @@ const ResourceFindingsTableComponent = ({ onChange={setTableOptions} pagination={pagination} sorting={sorting} + rowProps={getRowProps} /> {selectedFinding && ( ['from']>; - size: NonNullable['size']>; sort: Sort; enabled: boolean; } export interface ResourceFindingsQuery { pageIndex: Pagination['pageIndex']; - pageSize: Pagination['pageSize']; sort: Sort; } @@ -46,14 +42,11 @@ export type ResourceFindingsResponseAggs = Record< const getResourceFindingsQuery = ({ query, resourceId, - from, - size, - pitId, sort, -}: UseResourceFindingsOptions & { pitId: string }): estypes.SearchRequest => ({ +}: UseResourceFindingsOptions): estypes.SearchRequest => ({ + index: CSP_LATEST_FINDINGS_DATA_VIEW, body: { - from, - size, + size: MAX_FINDINGS_TO_LOAD, query: { ...query, bool: { @@ -62,7 +55,6 @@ const getResourceFindingsQuery = ({ }, }, sort: [{ [sort.field]: sort.direction }], - pit: { id: pitId }, aggs: { ...getFindingsCountAggQuery(), clusterId: { @@ -85,8 +77,7 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { notifications: { toasts }, } = useKibana().services; - const { pitIdRef, setPitId } = useContext(FindingsEsPitContext); - const params = { ...options, pitId: pitIdRef.current }; + const params = { ...options }; return useQuery( ['csp_resource_findings', { params }], @@ -99,9 +90,7 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { { enabled: options.enabled, keepPreviousData: true, - select: ({ - rawResponse: { hits, pit_id: newPitId, aggregations }, - }: ResourceFindingsResponse) => { + select: ({ rawResponse: { hits, aggregations } }: ResourceFindingsResponse) => { if (!aggregations) throw new Error('expected aggregations to exists'); assertNonEmptyArray(aggregations.count.buckets); @@ -116,16 +105,9 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { clusterId: getFirstBucketKey(aggregations.clusterId.buckets), resourceSubType: getFirstBucketKey(aggregations.resourceSubType.buckets), resourceName: getFirstBucketKey(aggregations.resourceName.buckets), - newPitId: newPitId!, }; }, onError: (err: Error) => showErrorToast(toasts, err), - onSuccess: ({ newPitId }) => { - setPitId(newPitId); - }, - // Refetching on an interval to ensure the PIT window stays open - refetchInterval: FINDINGS_REFETCH_INTERVAL_MS, - refetchIntervalInBackground: true, } ); }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/use_findings_by_resource.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/use_findings_by_resource.ts index 000100a8891395..139013d88cec49 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/use_findings_by_resource.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/use_findings_by_resource.ts @@ -4,22 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useContext } from 'react'; import { useQuery } from '@tanstack/react-query'; import { lastValueFrom } from 'rxjs'; import { IKibanaSearchRequest, IKibanaSearchResponse } from '@kbn/data-plugin/common'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { Pagination } from '@elastic/eui'; -import { FindingsEsPitContext } from '../es_pit/findings_es_pit_context'; -import { FINDINGS_REFETCH_INTERVAL_MS } from '../constants'; import { useKibana } from '../../../common/hooks/use_kibana'; import { showErrorToast } from '../latest_findings/use_latest_findings'; import type { FindingsBaseEsQuery, Sort } from '../types'; import { getAggregationCount, getFindingsCountAggQuery } from '../utils/utils'; +import { CSP_LATEST_FINDINGS_DATA_VIEW } from '../../../../common/constants'; +import { MAX_FINDINGS_TO_LOAD } from '../../../common/constants'; interface UseFindingsByResourceOptions extends FindingsBaseEsQuery { - from: NonNullable['from']>; - size: NonNullable['size']>; enabled: boolean; sortDirection: Sort['direction']; } @@ -27,13 +24,8 @@ interface UseFindingsByResourceOptions extends FindingsBaseEsQuery { // Maximum number of grouped findings, default limit in elasticsearch is set to 65,536 (ref: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-settings.html#search-settings-max-buckets) const MAX_BUCKETS = 60 * 1000; -interface UseResourceFindingsQueryOptions extends Omit { - pitId: string; -} - export interface FindingsByResourceQuery { pageIndex: Pagination['pageIndex']; - pageSize: Pagination['pageSize']; sortDirection: Sort['direction']; } @@ -73,11 +65,9 @@ interface FindingsAggBucket extends estypes.AggregationsStringRareTermsBucketKey export const getFindingsByResourceAggQuery = ({ query, - from, - size, - pitId, sortDirection, -}: UseResourceFindingsQueryOptions): estypes.SearchRequest => ({ +}: UseFindingsByResourceOptions): estypes.SearchRequest => ({ + index: CSP_LATEST_FINDINGS_DATA_VIEW, body: { query, size: 0, @@ -107,8 +97,7 @@ export const getFindingsByResourceAggQuery = ({ }, sort_failed_findings: { bucket_sort: { - from, - size, + size: MAX_FINDINGS_TO_LOAD, sort: [ { 'failed_findings>_count': { order: sortDirection }, @@ -121,7 +110,6 @@ export const getFindingsByResourceAggQuery = ({ }, }, }, - pit: { id: pitId }, }, ignore_unavailable: false, }); @@ -132,14 +120,13 @@ export const useFindingsByResource = (options: UseFindingsByResourceOptions) => notifications: { toasts }, } = useKibana().services; - const { pitIdRef, setPitId } = useContext(FindingsEsPitContext); - const params = { ...options, pitId: pitIdRef.current }; + const params = { ...options }; return useQuery( ['csp_findings_resource', { params }], async () => { const { - rawResponse: { aggregations, pit_id: newPitId }, + rawResponse: { aggregations }, } = await lastValueFrom( data.search.search({ params: getFindingsByResourceAggQuery(params), @@ -158,19 +145,12 @@ export const useFindingsByResource = (options: UseFindingsByResourceOptions) => page: aggregations.resources.buckets.map(createFindingsByResource), total: aggregations.resource_total.value, count: getAggregationCount(aggregations.count.buckets), - newPitId: newPitId!, }; }, { enabled: options.enabled, keepPreviousData: true, onError: (err: Error) => showErrorToast(toasts, err), - onSuccess: ({ newPitId }) => { - setPitId(newPitId); - }, - // Refetching on an interval to ensure the PIT window stays open - refetchInterval: FINDINGS_REFETCH_INTERVAL_MS, - refetchIntervalInBackground: true, } ); }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/utils/utils.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/utils/utils.ts index 27ea5dabc2e8e1..984f0d2dfcaf85 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/utils/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/utils/utils.ts @@ -6,12 +6,13 @@ */ import { buildEsQuery, type Query } from '@kbn/es-query'; -import { EuiBasicTableProps, Pagination } from '@elastic/eui'; +import type { EuiBasicTableProps, EuiThemeComputed, Pagination } from '@elastic/eui'; import { useCallback, useEffect, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import type { estypes } from '@elastic/elasticsearch'; import type { FindingsBaseProps, FindingsBaseURLQuery } from '../types'; import { useKibana } from '../../../common/hooks/use_kibana'; +import type { CspFinding } from '../../../../common/schemas/csp_finding'; export { getFilters } from './get_filters'; const getBaseQuery = ({ dataView, query, filters }: FindingsBaseURLQuery & FindingsBaseProps) => { @@ -128,3 +129,14 @@ export const getAggregationCount = (buckets: estypes.AggregationsStringRareTerms failed: failed?.doc_count || 0, }; }; + +const isSelectedRow = (row: CspFinding, selected?: CspFinding) => + row.resource.id === selected?.resource.id && row.rule.id === selected?.rule.id; + +export const getSelectedRowStyle = ( + theme: EuiThemeComputed, + row: CspFinding, + selected?: CspFinding +): React.CSSProperties => ({ + background: isSelectedRow(row, selected) ? theme.colors.highlight : undefined, +}); 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 84f39bb150c260..534d3d2f342372 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,7 +7,6 @@ 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, @@ -23,7 +22,8 @@ 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'; +import { LOCAL_STORAGE_PAGE_SIZE_RULES_KEY } from '../../common/constants'; +import { usePageSize } from '../../common/hooks/use_page_size'; interface RulesPageData { rules_page: RuleSavedObject[]; @@ -70,7 +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 { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE_RULES_KEY); const [rulesQuery, setRulesQuery] = useState({ filter: createCspRuleSearchFilterByPackagePolicy({ packagePolicyId: params.packagePolicyId, @@ -95,7 +95,7 @@ export const RulesContainer = () => { return (
- + setRulesQuery((currentQuery) => ({ ...currentQuery, search: value }))} searchValue={rulesQuery.search} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx index dad18c05aecb76..9dca77a4d017cc 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx @@ -14,7 +14,6 @@ import { useEuiTheme, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { TimestampTableCell } from '../../components/timestamp_table_cell'; import type { RulesState } from './rules_container'; import * as TEST_SUBJECTS from './test_subjects'; import type { RuleSavedObject } from './use_csp_rules'; @@ -113,12 +112,4 @@ const getColumns = ({ }), width: '15%', }, - { - field: 'updatedAt', - name: i18n.translate('xpack.csp.rules.rulesTable.lastModifiedColumnLabel', { - defaultMessage: 'Last Modified', - }), - width: '15%', - render: (timestamp) => , - }, ]; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx index e57404e63880e4..5f6e3887647c86 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx @@ -53,15 +53,16 @@ const SearchField = ({ useDebounce(() => search(localValue), SEARCH_DEBOUNCE_MS, [localValue]); return ( - + setLocalValue(e.target.value)} style={{ minWidth: 150 }} + fullWidth /> ); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/es_pit/es_pit.test.ts b/x-pack/plugins/cloud_security_posture/server/routes/es_pit/es_pit.test.ts deleted file mode 100644 index 8447e393f2f515..00000000000000 --- a/x-pack/plugins/cloud_security_posture/server/routes/es_pit/es_pit.test.ts +++ /dev/null @@ -1,147 +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 { Chance } from 'chance'; -import { - elasticsearchClientMock, - ElasticsearchClientMock, -} from '@kbn/core-elasticsearch-client-server-mocks'; -import type { ElasticsearchClient } from '@kbn/core/server'; -import { httpServerMock, httpServiceMock } from '@kbn/core/server/mocks'; -import { DEFAULT_PIT_KEEP_ALIVE, defineEsPitRoute, esPitInputSchema } from './es_pit'; -import { createCspRequestHandlerContextMock } from '../../mocks'; - -describe('ES Point in time API endpoint', () => { - const chance = new Chance(); - let mockEsClient: jest.Mocked; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('validate the API route path', () => { - const router = httpServiceMock.createRouter(); - - defineEsPitRoute(router); - - const [config] = router.post.mock.calls[0]; - expect(config.path).toEqual('/internal/cloud_security_posture/es_pit'); - }); - - it('should accept to a user with fleet.all privilege', async () => { - const router = httpServiceMock.createRouter(); - - defineEsPitRoute(router); - - const mockContext = createCspRequestHandlerContextMock(); - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest(); - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - const [_, handler] = router.post.mock.calls[0]; - await handler(context, req, res); - - expect(res.forbidden).toHaveBeenCalledTimes(0); - }); - - it('should reject to a user without fleet.all privilege', async () => { - const router = httpServiceMock.createRouter(); - - defineEsPitRoute(router); - - const mockContext = createCspRequestHandlerContextMock(); - mockContext.fleet.authz.fleet.all = false; - - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest(); - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - - const [_, handler] = router.post.mock.calls[0]; - await handler(context, req, res); - - expect(res.forbidden).toHaveBeenCalledTimes(1); - }); - - it('should return the newly created PIT ID from ES', async () => { - const router = httpServiceMock.createRouter(); - - defineEsPitRoute(router); - - const pitId = chance.string(); - mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; - mockEsClient.openPointInTime.mockImplementation(() => Promise.resolve({ id: pitId })); - - const mockContext = createCspRequestHandlerContextMock(); - mockContext.core.elasticsearch.client.asCurrentUser = mockEsClient as ElasticsearchClientMock; - - const indexName = chance.string(); - const keepAlive = chance.string(); - const mockResponse = httpServerMock.createResponseFactory(); - const mockRequest = httpServerMock.createKibanaRequest({ - query: { index_name: indexName, keep_alive: keepAlive }, - }); - - const [context, req, res] = [mockContext, mockRequest, mockResponse]; - const [_, handler] = router.post.mock.calls[0]; - await handler(context, req, res); - - expect(mockEsClient.openPointInTime).toHaveBeenCalledTimes(1); - expect(mockEsClient.openPointInTime).toHaveBeenLastCalledWith({ - index: indexName, - keep_alive: keepAlive, - }); - - expect(res.ok).toHaveBeenCalledTimes(1); - expect(res.ok).toHaveBeenLastCalledWith({ body: pitId }); - }); - - describe('test input schema', () => { - it('passes keep alive and index name parameters', () => { - const indexName = chance.string(); - const keepAlive = chance.string(); - const validatedQuery = esPitInputSchema.validate({ - index_name: indexName, - keep_alive: keepAlive, - }); - - expect(validatedQuery).toMatchObject({ - index_name: indexName, - keep_alive: keepAlive, - }); - }); - - it('populates default keep alive parameter value', () => { - const indexName = chance.string(); - const validatedQuery = esPitInputSchema.validate({ index_name: indexName }); - - expect(validatedQuery).toMatchObject({ - index_name: indexName, - keep_alive: DEFAULT_PIT_KEEP_ALIVE, - }); - }); - - it('throws when index name parameter is not passed', () => { - expect(() => { - esPitInputSchema.validate({}); - }).toThrow(); - }); - - it('throws when index name parameter is not a string', () => { - const indexName = chance.integer(); - expect(() => { - esPitInputSchema.validate({ index_name: indexName }); - }).toThrow(); - }); - - it('throws when keep alive parameter is not a string', () => { - const indexName = chance.string(); - const keepAlive = chance.integer(); - expect(() => { - esPitInputSchema.validate({ index_name: indexName, keep_alive: keepAlive }); - }).toThrow(); - }); - }); -}); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/es_pit/es_pit.ts b/x-pack/plugins/cloud_security_posture/server/routes/es_pit/es_pit.ts deleted file mode 100644 index beba811429aa58..00000000000000 --- a/x-pack/plugins/cloud_security_posture/server/routes/es_pit/es_pit.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { schema } from '@kbn/config-schema'; -import { transformError } from '@kbn/securitysolution-es-utils'; -import { ES_PIT_ROUTE_PATH } from '../../../common/constants'; -import type { CspRouter } from '../../types'; - -export const DEFAULT_PIT_KEEP_ALIVE = '1m'; - -export const esPitInputSchema = schema.object({ - index_name: schema.string(), - keep_alive: schema.string({ defaultValue: DEFAULT_PIT_KEEP_ALIVE }), -}); - -export const defineEsPitRoute = (router: CspRouter): void => - router.post( - { - path: ES_PIT_ROUTE_PATH, - validate: { query: esPitInputSchema }, - options: { - tags: ['access:cloud-security-posture-read'], - }, - }, - async (context, request, response) => { - const cspContext = await context.csp; - - if (!(await context.fleet).authz.fleet.all) { - return response.forbidden(); - } - - try { - const esClient = cspContext.esClient.asCurrentUser; - const { id } = await esClient.openPointInTime({ - index: request.query.index_name, - keep_alive: request.query.keep_alive, - }); - - return response.ok({ body: id }); - } catch (err) { - const error = transformError(err); - cspContext.logger.error(`Failed to open Elasticsearch point in time: ${error}`); - return response.customError({ - body: { message: error.message }, - statusCode: error.statusCode, - }); - } - } - ); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts b/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts index 4c2901edde56be..ed31e5fd6bf7a9 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts @@ -16,7 +16,6 @@ import { PLUGIN_ID } from '../../common'; import { defineGetComplianceDashboardRoute } from './compliance_dashboard/compliance_dashboard'; import { defineGetBenchmarksRoute } from './benchmarks/benchmarks'; import { defineGetCspStatusRoute } from './status/status'; -import { defineEsPitRoute } from './es_pit/es_pit'; /** * 1. Registers routes @@ -33,7 +32,6 @@ export function setupRoutes({ defineGetComplianceDashboardRoute(router); defineGetBenchmarksRoute(router); defineGetCspStatusRoute(router); - defineEsPitRoute(router); core.http.registerRouteHandlerContext( PLUGIN_ID, diff --git a/x-pack/plugins/data_visualizer/common/types/field_request_config.ts b/x-pack/plugins/data_visualizer/common/types/field_request_config.ts index f7a688f4acd62f..d559d5d46f5286 100644 --- a/x-pack/plugins/data_visualizer/common/types/field_request_config.ts +++ b/x-pack/plugins/data_visualizer/common/types/field_request_config.ts @@ -64,9 +64,7 @@ export interface FieldVisStats { max?: number; median?: number; min?: number; - topValues?: Array<{ key: number | string; doc_count: number }>; - topValuesSampleSize?: number; - topValuesSamplerShardSize?: number; + topValues?: Array<{ key: number | string; doc_count: number; percent: number }>; examples?: Array; timeRangeEarliest?: number; timeRangeLatest?: number; diff --git a/x-pack/plugins/data_visualizer/common/types/field_stats.ts b/x-pack/plugins/data_visualizer/common/types/field_stats.ts index 5aef2b442c1cb3..654114923eb2b8 100644 --- a/x-pack/plugins/data_visualizer/common/types/field_stats.ts +++ b/x-pack/plugins/data_visualizer/common/types/field_stats.ts @@ -11,6 +11,25 @@ import { IKibanaSearchResponse } from '@kbn/data-plugin/common'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { TimeBucketsInterval } from '../services/time_buckets'; +export interface RandomSamplingOption { + mode: 'random_sampling'; + seed: string; + probability: number; +} + +export interface NormalSamplingOption { + mode: 'normal_sampling'; + seed: string; + shardSize: number; +} + +export interface NoSamplingOption { + mode: 'no_sampling'; + seed: string; +} + +export type SamplingOption = RandomSamplingOption | NormalSamplingOption | NoSamplingOption; + export interface FieldData { fieldName: string; existsInDocs: boolean; @@ -54,7 +73,7 @@ export const isIKibanaSearchResponse = (arg: unknown): arg is IKibanaSearchRespo export interface NumericFieldStats { fieldName: string; - count: number; + count?: number; min: number; max: number; avg: number; @@ -86,7 +105,8 @@ export interface BooleanFieldStats { count: number; trueCount: number; falseCount: number; - [key: string]: number | string; + topValues: Bucket[]; + topValuesSampleSize: number; } export interface DocumentCountStats { @@ -186,6 +206,9 @@ export interface FieldStatsCommonRequestParams { intervalMs?: number; query: estypes.QueryDslQueryContainer; maxExamples?: number; + samplingProbability: number | null; + browserSessionSeed: number; + samplingOption: SamplingOption; } export interface OverallStatsSearchStrategyParams { @@ -202,6 +225,8 @@ export interface OverallStatsSearchStrategyParams { aggregatableFields: string[]; nonAggregatableFields: string[]; fieldsToFetch?: string[]; + browserSessionSeed: number; + samplingOption: SamplingOption; } export interface FieldStatsSearchStrategyReturnBase { @@ -238,3 +263,20 @@ export interface Field { export interface Aggs { [key: string]: estypes.AggregationsAggregationContainer; } + +export const EMBEDDABLE_SAMPLER_OPTION = { + RANDOM: 'random_sampling', + NORMAL: 'normal_sampling', +}; +export type FieldStatsEmbeddableSamplerOption = + typeof EMBEDDABLE_SAMPLER_OPTION[keyof typeof EMBEDDABLE_SAMPLER_OPTION]; + +export function isRandomSamplingOption(arg: SamplingOption): arg is RandomSamplingOption { + return arg.mode === 'random_sampling'; +} +export function isNormalSamplingOption(arg: SamplingOption): arg is NormalSamplingOption { + return arg.mode === 'normal_sampling'; +} +export function isNoSamplingOption(arg: SamplingOption): arg is NoSamplingOption { + return arg.mode === 'no_sampling' || (arg.mode === 'random_sampling' && arg.probability === 1); +} diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_content.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_content.tsx index a2eb458bbdf464..09b59cf10abb55 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_content.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_content.tsx @@ -20,7 +20,7 @@ import { EuiFormRow, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { sortedIndex } from 'lodash'; +import { debounce, sortedIndex } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; import { isDefined } from '../../util/is_defined'; import type { DocumentCountChartPoint } from './document_count_chart'; @@ -64,6 +64,24 @@ export const DocumentCountContent: FC = ({ setShowSamplingOptionsPopover(false); }, [setShowSamplingOptionsPopover]); + // eslint-disable-next-line react-hooks/exhaustive-deps + const updateSamplingProbability = useCallback( + debounce((newProbability: number) => { + if (setSamplingProbability) { + const idx = sortedIndex(RANDOM_SAMPLER_PROBABILITIES, newProbability); + const closestPrev = RANDOM_SAMPLER_PROBABILITIES[idx - 1]; + const closestNext = RANDOM_SAMPLER_PROBABILITIES[idx]; + const closestProbability = + Math.abs(closestPrev - newProbability) < Math.abs(closestNext - newProbability) + ? closestPrev + : closestNext; + + setSamplingProbability(closestProbability / 100); + } + }, 100), + [setSamplingProbability] + ); + const calloutInfoMessage = useMemo(() => { switch (randomSamplerPreference) { case RANDOM_SAMPLER_OPTION.OFF: @@ -125,7 +143,7 @@ export const DocumentCountContent: FC = ({ <> - + = ({ value: d, label: d === 0.001 || d >= 5 ? `${d}%` : '', }))} - onChange={(e) => { - const newProbability = Number(e.currentTarget.value); - const idx = sortedIndex(RANDOM_SAMPLER_PROBABILITIES, newProbability); - const closestPrev = RANDOM_SAMPLER_PROBABILITIES[idx - 1]; - const closestNext = RANDOM_SAMPLER_PROBABILITIES[idx]; - const closestProbability = - Math.abs(closestPrev - newProbability) < - Math.abs(closestNext - newProbability) - ? closestPrev - : closestNext; - - if (setSamplingProbability) { - setSamplingProbability(closestProbability / 100); - } - }} + onChange={(e) => updateSamplingProbability(Number(e.currentTarget.value))} step={RANDOM_SAMPLER_STEP} data-test-subj="dvRandomSamplerProbabilityRange" /> diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/fields_stats_grid/fields_stats_grid.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/fields_stats_grid/fields_stats_grid.tsx index ec6243d45d5088..09fbc24f117640 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/fields_stats_grid/fields_stats_grid.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/fields_stats_grid/fields_stats_grid.tsx @@ -112,6 +112,7 @@ export const FieldsStatsGrid: FC = ({ results }) => { pageState={dataVisualizerListState} updatePageState={setDataVisualizerListState} getItemIdToExpandedRowMap={getItemIdToExpandedRowMap} + overallStatsRunning={false} />
); diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/boolean_content.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/boolean_content.tsx index 210f69c435a450..9d899de65604fe 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/boolean_content.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/boolean_content.tsx @@ -10,7 +10,6 @@ import { EuiSpacer } from '@elastic/eui'; import { Axis, BarSeries, Chart, Settings, ScaleType } from '@elastic/charts'; import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; import { TopValues } from '../../../top_values'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; @@ -45,32 +44,13 @@ export const BooleanContent: FC = ({ config, onAddFilter }) = const theme = useDataVizChartTheme(); if (!formattedPercentages) return null; - const { trueCount, falseCount, count } = formattedPercentages; - const stats = { - ...config.stats, - topValues: [ - { - key: i18n.translate( - 'xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.trueCountLabel', - { defaultMessage: 'true' } - ), - doc_count: trueCount ?? 0, - }, - { - key: i18n.translate( - 'xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.falseCountLabel', - { defaultMessage: 'false' } - ), - doc_count: falseCount ?? 0, - }, - ], - }; + const { count } = formattedPercentages; return ( = ({ stats, suggestion }) => { - const { fieldName, isTopValuesSampled, topValues, topValuesSamplerShardSize } = stats!; + const { + services: { + data: { fieldFormats }, + }, + } = useDataVisualizerKibana(); + + const { fieldName, isTopValuesSampled, topValues, sampleCount } = stats!; const layerList: VectorLayerDescriptor[] = useMemo( () => [getChoroplethTopValuesLayer(fieldName || '', topValues || [], suggestion)], [suggestion, fieldName, topValues] ); + if (!stats) return null; + + const totalDocuments = stats.totalDocuments ?? sampleCount ?? 0; + + const countsElement = totalDocuments ? ( + + {isTopValuesSampled ? ( + + {fieldFormats + .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) + .convert(sampleCount)} + + ), + }} + /> + ) : ( + + {fieldFormats + .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) + .convert(totalDocuments ?? 0)} + + ), + }} + /> + )} + + ) : null; + return ( = ({ stats, suggestion }) => { - {isTopValuesSampled === true && ( -
- - - - -
- )} + {countsElement}
); }; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/document_stats.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/document_stats.tsx index 0790d1d8c4b815..bb22295dfa7e16 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/document_stats.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_expanded_row/document_stats.tsx @@ -10,7 +10,7 @@ import React, { FC, ReactNode } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable, HorizontalAlignment, LEFT_ALIGNMENT, RIGHT_ALIGNMENT } from '@elastic/eui'; import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; -import { FieldDataRowProps } from '../../types'; +import { FieldDataRowProps, isIndexBasedFieldVisConfig } from '../../types'; import { roundToDecimalPlace } from '../../../utils'; import { ExpandedRowPanel } from './expanded_row_panel'; @@ -46,6 +46,13 @@ export const DocumentStatsTable: FC = ({ config }) => { ) return null; const { cardinality, count, sampleCount } = config.stats; + + const valueCount = + count ?? (isIndexBasedFieldVisConfig(config) && config.existsInDocs === true ? undefined : 0); + const docsPercent = + valueCount !== undefined && sampleCount !== undefined + ? roundToDecimalPlace((valueCount / sampleCount) * 100) + : undefined; const metaTableItems = [ { function: 'count', @@ -57,16 +64,20 @@ export const DocumentStatsTable: FC = ({ config }) => { ), value: count, }, - { - function: 'percentage', - display: ( - - ), - value: `${roundToDecimalPlace((count / sampleCount) * 100)}%`, - }, + ...(docsPercent !== undefined + ? [ + { + function: 'percentage', + display: ( + + ), + value: `${docsPercent}%`, + }, + ] + : []), { function: 'distinctValues', display: ( diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx index 01b8f0af9538db..a90e9fe563ee03 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/components/field_data_row/document_stats.tsx @@ -8,32 +8,46 @@ import { EuiIcon, EuiText } from '@elastic/eui'; import React from 'react'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; +import { useDataVisualizerKibana } from '../../../../../kibana_context'; +import { isIndexBasedFieldVisConfig } from '../../../../../../../common/types/field_vis_config'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { roundToDecimalPlace } from '../../../utils'; -import { isIndexBasedFieldVisConfig } from '../../types'; interface Props extends FieldDataRowProps { showIcon?: boolean; + totalCount?: number; } -export const DocumentStat = ({ config, showIcon }: Props) => { +export const DocumentStat = ({ config, showIcon, totalCount }: Props) => { const { stats } = config; + const { + services: { + data: { fieldFormats }, + }, + } = useDataVisualizerKibana(); + if (stats === undefined) return null; + const { count, sampleCount } = stats; + const total = sampleCount ?? totalCount; // If field exists is docs but we don't have count stats then don't show // Otherwise if field doesn't appear in docs at all, show 0% - const docsCount = + const valueCount = count ?? (isIndexBasedFieldVisConfig(config) && config.existsInDocs === true ? undefined : 0); const docsPercent = - docsCount !== undefined && sampleCount !== undefined - ? roundToDecimalPlace((docsCount / sampleCount) * 100) - : 0; + valueCount !== undefined && total !== undefined + ? `(${roundToDecimalPlace((valueCount / total) * 100)}%)` + : null; - return docsCount !== undefined ? ( + return valueCount !== undefined ? ( <> {showIcon ? : null} - {docsCount} ({docsPercent}%) + {fieldFormats + .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) + .convert(valueCount)}{' '} + {docsPercent} ) : null; diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx index a0006431d9d722..081c062979f7ae 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/stats_table/data_visualizer_stats_table.tsx @@ -60,6 +60,8 @@ interface DataVisualizerTableProps { /** Callback to receive any updates when table or page state is changed **/ onChange?: (update: Partial) => void; loading?: boolean; + totalCount?: number; + overallStatsRunning: boolean; } export const DataVisualizerTable = ({ @@ -71,6 +73,8 @@ export const DataVisualizerTable = ({ showPreviewByDefault, onChange, loading, + totalCount, + overallStatsRunning, }: DataVisualizerTableProps) => { const { euiTheme } = useEuiTheme(); @@ -217,12 +221,40 @@ export const DataVisualizerTable = ({ }, { field: 'docCount', - name: i18n.translate('xpack.dataVisualizer.dataGrid.documentsCountColumnName', { - defaultMessage: 'Documents (%)', - }), - render: (value: number | undefined, item: DataVisualizerTableItem) => ( - + name: ( +
+ {i18n.translate('xpack.dataVisualizer.dataGrid.documentsCountColumnName', { + defaultMessage: 'Documents (%)', + })} + { + + + + } +
), + + render: (value: number | undefined, item: DataVisualizerTableItem) => { + if (overallStatsRunning) { + return ( + + + + ); + } + + return ( + + ); + }, sortable: (item: DataVisualizerTableItem) => item?.stats?.count, align: LEFT_ALIGNMENT as HorizontalAlignment, 'data-test-subj': 'dataVisualizerTableColumnDocumentsCount', @@ -233,9 +265,19 @@ export const DataVisualizerTable = ({ name: i18n.translate('xpack.dataVisualizer.dataGrid.distinctValuesColumnName', { defaultMessage: 'Distinct values', }), - render: (_: undefined, item: DataVisualizerTableItem) => ( - - ), + render: (_: undefined, item: DataVisualizerTableItem) => { + if (overallStatsRunning) { + return ( + + + + ); + } + + return ( + + ); + }, sortable: (item: DataVisualizerTableItem) => item?.stats?.cardinality, align: LEFT_ALIGNMENT as HorizontalAlignment, 'data-test-subj': 'dataVisualizerTableColumnDistinctValues', @@ -333,6 +375,7 @@ export const DataVisualizerTable = ({ extendedColumns, dimensions.breakPoint, toggleExpandAll, + overallStatsRunning, ]); const itemIdToExpandedRowMap = useMemo(() => { diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx index e151f6a0e0240a..dec10fb5284228 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/top_values/top_values.tsx @@ -36,8 +36,7 @@ interface Props { onAddFilter?: (field: DataViewField | string, value: string, type: '+' | '-') => void; } -function getPercentLabel(docCount: number, topValuesSampleSize: number): string { - const percent = (100 * docCount) / topValuesSampleSize; +function getPercentLabel(percent: number): string { if (percent >= 0.1) { return `${roundToDecimalPlace(percent, 1)}%`; } else { @@ -47,76 +46,54 @@ function getPercentLabel(docCount: number, topValuesSampleSize: number): string export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed, onAddFilter }) => { const { - services: { data }, + services: { + data: { fieldFormats }, + }, } = useDataVisualizerKibana(); - const { fieldFormats } = data; - if (stats === undefined || !stats.topValues) return null; - const { - topValues, - topValuesSampleSize, - count, - isTopValuesSampled, - fieldName, - sampleCount, - topValuesSamplerShardSize, - } = stats; - - const totalDocuments = stats.totalDocuments; + const { topValues, fieldName, sampleCount } = stats; - const progressBarMax = isTopValuesSampled === true ? topValuesSampleSize : count; + const totalDocuments = stats.totalDocuments ?? sampleCount ?? 0; + const topValuesOtherCountPercent = + 1 - (topValues ? topValues.reduce((acc, bucket) => acc + bucket.percent, 0) : 0); + const topValuesOtherCount = Math.floor(topValuesOtherCountPercent * (sampleCount ?? 0)); - const topValuesOtherCount = - (progressBarMax ?? 0) - - (topValues ? topValues.map((value) => value.doc_count).reduce((v, acc) => acc + v, 0) : 0); - - const countsElement = - totalDocuments !== undefined ? ( - - {isTopValuesSampled ? ( - - {fieldFormats - .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) - .convert(sampleCount)} - - ), - }} - /> - ) : ( - - {fieldFormats - .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) - .convert(totalDocuments ?? 0)} - - ), - }} - /> - )} - - ) : ( - + const countsElement = ( + + {totalDocuments > (sampleCount ?? 0) ? ( + {fieldFormats + .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) + .convert(sampleCount)} + + ), }} /> - - ); + ) : ( + + {fieldFormats + .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) + .convert(totalDocuments ?? 0)} + + ), + }} + /> + )} + + ); return ( = ({ stats, fieldFormat, barColor, compressed, @@ -222,7 +199,7 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed, = ({ stats, fieldFormat, barColor, compressed, } className={classNames('eui-textTruncate', 'topValuesValueLabelContainer')} valueText={`${topValuesOtherCount}${ - progressBarMax !== undefined - ? ` (${getPercentLabel(topValuesOtherCount, progressBarMax)})` + totalDocuments !== undefined + ? ` (${getPercentLabel(topValuesOtherCountPercent * 100)})` : '' }`} /> @@ -249,12 +226,10 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed, ) : null} - {isTopValuesSampled === true && ( - - - {countsElement} - - )} + + + {countsElement} + ); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index 2e95f86e5291d2..30ff09f9c492e4 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -551,8 +551,10 @@ export const IndexDataVisualizerView: FC = (dataVi getItemIdToExpandedRowMap={getItemIdToExpandedRowMap} extendedColumns={extendedColumns} loading={progress < 100} + overallStatsRunning={overallStatsProgress.isRunning} showPreviewByDefault={dataVisualizerListState.showDistributions ?? true} onChange={setDataVisualizerListState} + totalCount={overallStats.totalCount} />
diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx index ac3d9c05c1b971..5b47bb820b9ab0 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx @@ -11,8 +11,8 @@ import { i18n } from '@kbn/i18n'; import { Query, Filter } from '@kbn/es-query'; import type { TimeRange } from '@kbn/es-query'; import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; +import { css } from '@emotion/react'; import { isDefined } from '../../../common/util/is_defined'; -import { ShardSizeFilter } from './shard_size_select'; import { DataVisualizerFieldNamesFilter } from './field_name_filter'; import { DataVisualizerFieldTypeFilter } from './field_type_filter'; import { SupportedFieldType } from '../../../../../common/types'; @@ -147,12 +147,15 @@ export const SearchPanel: FC = ({ />
- - - + { - return { - value: String(v), - inputDisplay: - v > 0 ? ( - - {v} }} - /> - - ) : ( - - - - ), - }; -}); - -export const ShardSizeFilter: FC = ({ samplerShardSize, setSamplerShardSize }) => { - return ( - - - setSamplerShardSize(+value)} - aria-label={i18n.translate('xpack.dataVisualizer.searchPanel.sampleSizeAriaLabel', { - defaultMessage: 'Select number of documents to sample', - })} - data-test-subj="dataVisualizerShardSizeSelect" - /> - - - - - - ); -}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx index 20b291120113e1..1b89ca7e68c36a 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/grid_embeddable/grid_embeddable.tsx @@ -24,6 +24,7 @@ import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-pl import type { Query } from '@kbn/es-query'; import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { SavedSearch } from '@kbn/discover-plugin/public'; +import { SamplingOption } from '../../../../../common/types/field_stats'; import { DATA_VISUALIZER_GRID_EMBEDDABLE_TYPE } from './constants'; import { EmbeddableLoading } from './embeddable_loading_fallback'; import { DataVisualizerStartDependencies } from '../../../../plugin'; @@ -34,7 +35,7 @@ import { import { FieldVisConfig } from '../../../common/components/stats_table/types'; import { getDefaultDataVisualizerListState } from '../../components/index_data_visualizer_view/index_data_visualizer_view'; import type { DataVisualizerTableState, SavedSearchSavedObject } from '../../../../../common/types'; -import { DataVisualizerIndexBasedAppState } from '../../types/index_data_visualizer_state'; +import type { DataVisualizerIndexBasedAppState } from '../../types/index_data_visualizer_state'; import { IndexBasedDataVisualizerExpandedRow } from '../../../common/components/expanded_row/index_based_expanded_row'; import { useDataVisualizerGridData } from '../../hooks/use_data_visualizer_grid_data'; @@ -55,6 +56,7 @@ export interface DataVisualizerGridInput { sessionId?: string; fieldsToFetch?: string[]; totalDocuments?: number; + samplingOption?: SamplingOption; } export type DataVisualizerGridEmbeddableInput = EmbeddableInput & DataVisualizerGridInput; export type DataVisualizerGridEmbeddableOutput = EmbeddableOutput; @@ -83,8 +85,15 @@ export const EmbeddableWrapper = ({ [dataVisualizerListState, onOutputChange] ); - const { configs, searchQueryLanguage, searchString, extendedColumns, progress, setLastRefresh } = - useDataVisualizerGridData(input, dataVisualizerListState); + const { + configs, + searchQueryLanguage, + searchString, + extendedColumns, + progress, + overallStatsProgress, + setLastRefresh, + } = useDataVisualizerGridData(input, dataVisualizerListState); useEffect(() => { setLastRefresh(Date.now()); @@ -143,6 +152,7 @@ export const EmbeddableWrapper = ({ showPreviewByDefault={input?.showPreviewByDefault} onChange={onOutputChange} loading={progress < 100} + overallStatsRunning={overallStatsProgress.isRunning} /> ); }; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts index 695412b8bf0b53..30bd9b56a75621 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts @@ -5,22 +5,23 @@ * 2.0. */ -import { Required } from 'utility-types'; +import type { Required } from 'utility-types'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { merge } from 'rxjs'; -import { EuiTableActionsColumnType } from '@elastic/eui/src/components/basic_table/table_types'; +import type { EuiTableActionsColumnType } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { DataViewField, KBN_FIELD_TYPES, UI_SETTINGS } from '@kbn/data-plugin/common'; import seedrandom from 'seedrandom'; -import { RandomSamplerOption } from '../constants/random_sampler'; -import { DataVisualizerIndexBasedAppState } from '../types/index_data_visualizer_state'; +import type { SamplingOption } from '@kbn/discover-plugin/public/application/main/components/field_stats_table/field_stats_table'; +import type { RandomSamplerOption } from '../constants/random_sampler'; +import type { DataVisualizerIndexBasedAppState } from '../types/index_data_visualizer_state'; import { useDataVisualizerKibana } from '../../kibana_context'; import { getEsQueryFromSavedSearch } from '../utils/saved_search_utils'; -import { MetricFieldsStats } from '../../common/components/stats_table/components/field_count_stats'; +import type { MetricFieldsStats } from '../../common/components/stats_table/components/field_count_stats'; import { useTimefilter } from './use_time_filter'; import { dataVisualizerRefresh$ } from '../services/timefilter_refresh_service'; import { TimeBuckets } from '../../../../common/services/time_buckets'; -import { FieldVisConfig } from '../../common/components/stats_table/types'; +import type { FieldVisConfig } from '../../common/components/stats_table/types'; import { SUPPORTED_FIELD_TYPES, NON_AGGREGATABLE_FIELD_TYPES, @@ -29,13 +30,13 @@ import { import type { FieldRequestConfig, SupportedFieldType } from '../../../../common/types'; import { kbnTypeToJobType } from '../../common/util/field_types_utils'; import { getActions } from '../../common/components/field_data_row/action_menu'; -import { DataVisualizerGridInput } from '../embeddables/grid_embeddable/grid_embeddable'; +import type { DataVisualizerGridInput } from '../embeddables/grid_embeddable/grid_embeddable'; import { getDefaultPageState } from '../components/index_data_visualizer_view/index_data_visualizer_view'; import { useFieldStatsSearchStrategy } from './use_field_stats'; import { useOverallStats } from './use_overall_stats'; -import { OverallStatsSearchStrategyParams } from '../../../../common/types/field_stats'; -import { Dictionary } from '../../common/util/url_state'; -import { AggregatableField, NonAggregatableField } from '../types/overall_stats'; +import type { OverallStatsSearchStrategyParams } from '../../../../common/types/field_stats'; +import type { Dictionary } from '../../common/util/url_state'; +import type { AggregatableField, NonAggregatableField } from '../types/overall_stats'; const defaults = getDefaultPageState(); @@ -43,6 +44,11 @@ function isDisplayField(fieldName: string): boolean { return !OMIT_FIELDS.includes(fieldName); } +const DEFAULT_SAMPLING_OPTION: SamplingOption = { + mode: 'random_sampling', + seed: '', + probability: 0, +}; export const useDataVisualizerGridData = ( input: DataVisualizerGridInput, dataVisualizerListState: Required, @@ -76,6 +82,7 @@ export const useDataVisualizerGridData = ( currentFilters, visibleFieldNames, fieldsToFetch, + samplingOption, } = useMemo( () => ({ currentSavedSearch: input?.savedSearch, @@ -84,6 +91,8 @@ export const useDataVisualizerGridData = ( visibleFieldNames: input?.visibleFieldNames ?? [], currentFilters: input?.filters, fieldsToFetch: input?.fieldsToFetch, + /** By default, use random sampling **/ + samplingOption: input?.samplingOption ?? DEFAULT_SAMPLING_OPTION, }), [input] ); @@ -203,6 +212,7 @@ export const useDataVisualizerGridData = ( } } }); + return { earliest, latest, @@ -217,6 +227,8 @@ export const useDataVisualizerGridData = ( aggregatableFields, nonAggregatableFields, fieldsToFetch, + browserSessionSeed, + samplingOption: { ...samplingOption, seed: browserSessionSeed.toString() }, }; }, // eslint-disable-next-line react-hooks/exhaustive-deps @@ -226,17 +238,19 @@ export const useDataVisualizerGridData = ( currentDataView.id, // eslint-disable-next-line react-hooks/exhaustive-deps JSON.stringify(searchQuery), + // eslint-disable-next-line react-hooks/exhaustive-deps + JSON.stringify(samplingOption), samplerShardSize, searchSessionId, lastRefresh, fieldsToFetch, + browserSessionSeed, ] ); const { overallStats, progress: overallStatsProgress } = useOverallStats( fieldStatsRequest, lastRefresh, - browserSessionSeed, dataVisualizerListState.probability ); @@ -269,10 +283,20 @@ export const useDataVisualizerGridData = ( return { metricConfigs: existMetricFields, nonMetricConfigs: existNonMetricFields }; }, [metricConfigs, nonMetricConfigs, overallStatsProgress.loaded]); + const probability = useMemo( + () => + // If random sampler probability is already manually selected, or is available from the URL + // use that instead of using the probability calculated from the doc count + (dataVisualizerListState.probability === null + ? overallStats?.documentCountStats?.probability + : dataVisualizerListState.probability) ?? 1, + [dataVisualizerListState.probability, overallStats?.documentCountStats?.probability] + ); const strategyResponse = useFieldStatsSearchStrategy( fieldStatsRequest, configsWithoutStats, - dataVisualizerListState + dataVisualizerListState, + probability ); const combinedProgress = useMemo( diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_field_stats.ts index b6b1a21f312f9a..2a91f9aa5366bc 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_field_stats.ts @@ -65,7 +65,8 @@ const createBatchedRequests = (fields: Field[], maxBatchSize = 10) => { export function useFieldStatsSearchStrategy( searchStrategyParams: OverallStatsSearchStrategyParams | undefined, fieldStatsParams: FieldStatsParams | undefined, - dataVisualizerListState: DataVisualizerIndexBasedAppState + dataVisualizerListState: DataVisualizerIndexBasedAppState, + samplingProbability: number | null ): FieldStatsSearchStrategyReturnBase { const { services: { @@ -168,6 +169,9 @@ export function useFieldStatsSearchStrategy( }, }, maxExamples: MAX_EXAMPLES_DEFAULT, + samplingProbability, + browserSessionSeed: searchStrategyParams.browserSessionSeed, + samplingOption: searchStrategyParams.samplingOption, }; const searchOptions: ISearchOptions = { abortSignal: abortCtrl.current.signal, @@ -295,6 +299,7 @@ export function useFieldStatsSearchStrategy( dataVisualizerListState.pageIndex, dataVisualizerListState.sortDirection, dataVisualizerListState.sortField, + samplingProbability, ]); const cancelFetch = useCallback(() => { diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts index 1d4bb35558085a..a3bf2b7b0cd64b 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_overall_stats.ts @@ -30,14 +30,14 @@ import { import type { OverallStats } from '../types/overall_stats'; import { getDefaultPageState } from '../components/index_data_visualizer_view/index_data_visualizer_view'; import { extractErrorProperties } from '../utils/error_utils'; -import type { +import { DataStatsFetchProgress, + isRandomSamplingOption, OverallStatsSearchStrategyParams, } from '../../../../common/types/field_stats'; import { getDocumentCountStats } from '../search_strategy/requests/get_document_stats'; import { getInitialProgress, getReducer } from '../progress_utils'; import { MAX_CONCURRENT_REQUESTS } from '../constants/index_data_visualizer_viewer'; -import { DocumentCountStats } from '../../../../common/types/field_stats'; /** * Helper function to run forkJoin @@ -92,7 +92,6 @@ function displayError(toastNotifications: ToastsStart, index: string, err: any) export function useOverallStats( searchStrategyParams: TParams | undefined, lastRefresh: number, - browserSessionSeed: number, probability?: number | null ): { progress: DataStatsFetchProgress; @@ -114,167 +113,163 @@ export function useOverallStats(); - const startFetch = useCallback(() => { - searchSubscription$.current?.unsubscribe(); - abortCtrl.current.abort(); - abortCtrl.current = new AbortController(); + const startFetch = useCallback(async () => { + try { + searchSubscription$.current?.unsubscribe(); + abortCtrl.current.abort(); + abortCtrl.current = new AbortController(); - if (!searchStrategyParams || lastRefresh === 0) return; + if (!searchStrategyParams || lastRefresh === 0) return; - setFetchState({ - ...getInitialProgress(), - error: undefined, - }); + setFetchState({ + ...getInitialProgress(), + isRunning: true, + error: undefined, + }); - const { - aggregatableFields, - nonAggregatableFields, - index, - searchQuery, - timeFieldName, - earliest, - latest, - runtimeFieldMap, - samplerShardSize, - } = searchStrategyParams; + const { + aggregatableFields, + nonAggregatableFields, + index, + searchQuery, + timeFieldName, + earliest, + latest, + runtimeFieldMap, + samplingOption, + } = searchStrategyParams; - const searchOptions: ISearchOptions = { - abortSignal: abortCtrl.current.signal, - sessionId: searchStrategyParams?.sessionId, - }; + const searchOptions: ISearchOptions = { + abortSignal: abortCtrl.current.signal, + sessionId: searchStrategyParams?.sessionId, + }; - const nonAggregatableFieldsObs = nonAggregatableFields.map((fieldName: string) => - data.search - .search( - { - params: checkNonAggregatableFieldExistsRequest( - index, - searchQuery, - fieldName, - timeFieldName, - earliest, - latest, - runtimeFieldMap - ), - }, - searchOptions - ) - .pipe( - map((resp) => { - return { - ...resp, - rawResponse: { ...resp.rawResponse, fieldName }, - } as IKibanaSearchResponse; - }) - ) - ); + const documentCountStats = await getDocumentCountStats( + data.search, + searchStrategyParams, + searchOptions, + samplingOption.seed, + probability + ); - // Have to divide into smaller requests to avoid 413 payload too large - const aggregatableFieldsChunks = chunk(aggregatableFields, 30); + const nonAggregatableFieldsObs = nonAggregatableFields.map((fieldName: string) => + data.search + .search( + { + params: checkNonAggregatableFieldExistsRequest( + index, + searchQuery, + fieldName, + timeFieldName, + earliest, + latest, + runtimeFieldMap + ), + }, + searchOptions + ) + .pipe( + map((resp) => { + return { + ...resp, + rawResponse: { ...resp.rawResponse, fieldName }, + } as IKibanaSearchResponse; + }) + ) + ); - const aggregatableOverallStatsObs = aggregatableFieldsChunks.map((aggregatableFieldsChunk) => - data.search - .search( - { - params: checkAggregatableFieldsExistRequest( - index, - searchQuery, - aggregatableFieldsChunk, - samplerShardSize, - timeFieldName, - earliest, - latest, - undefined, - runtimeFieldMap - ), - }, - searchOptions - ) - .pipe( - map((resp) => { - return { - ...resp, - aggregatableFields: aggregatableFieldsChunk, - } as AggregatableFieldOverallStats; - }) - ) - ); + // Have to divide into smaller requests to avoid 413 payload too large + const aggregatableFieldsChunks = chunk(aggregatableFields, 30); - const sub = rateLimitingForkJoin< - | DocumentCountStats - | AggregatableFieldOverallStats - | NonAggregatableFieldOverallStats - | undefined - >( - [ - from( - getDocumentCountStats( - data.search, - searchStrategyParams, - searchOptions, - browserSessionSeed, - probability + if (isRandomSamplingOption(samplingOption)) { + samplingOption.probability = documentCountStats.probability ?? 1; + } + const aggregatableOverallStatsObs = aggregatableFieldsChunks.map((aggregatableFieldsChunk) => + data.search + .search( + { + params: checkAggregatableFieldsExistRequest( + index, + searchQuery, + aggregatableFieldsChunk, + samplingOption, + timeFieldName, + earliest, + latest, + undefined, + runtimeFieldMap + ), + }, + searchOptions ) - ), - ...aggregatableOverallStatsObs, - ...nonAggregatableFieldsObs, - ], - MAX_CONCURRENT_REQUESTS - ); + .pipe( + map((resp) => { + return { + ...resp, + aggregatableFields: aggregatableFieldsChunk, + } as AggregatableFieldOverallStats; + }) + ) + ); - searchSubscription$.current = sub.subscribe({ - next: (value) => { - const aggregatableOverallStatsResp: AggregatableFieldOverallStats[] = []; - const nonAggregatableOverallStatsResp: NonAggregatableFieldOverallStats[] = []; - const documentCountStats = value[0] as DocumentCountStats; + const sub = rateLimitingForkJoin< + AggregatableFieldOverallStats | NonAggregatableFieldOverallStats | undefined + >([...aggregatableOverallStatsObs, ...nonAggregatableFieldsObs], MAX_CONCURRENT_REQUESTS); - value.forEach((resp, idx) => { - if (!resp || idx === 0) return; - if (isAggregatableFieldOverallStats(resp)) { - aggregatableOverallStatsResp.push(resp); - } + searchSubscription$.current = sub.subscribe({ + next: (value) => { + const aggregatableOverallStatsResp: AggregatableFieldOverallStats[] = []; + const nonAggregatableOverallStatsResp: NonAggregatableFieldOverallStats[] = []; - if (isNonAggregatableFieldOverallStats(resp)) { - nonAggregatableOverallStatsResp.push(resp); - } - }); + value.forEach((resp, idx) => { + if (isAggregatableFieldOverallStats(resp)) { + aggregatableOverallStatsResp.push(resp); + } - const totalCount = documentCountStats?.totalCount ?? 0; + if (isNonAggregatableFieldOverallStats(resp)) { + nonAggregatableOverallStatsResp.push(resp); + } + }); - const aggregatableOverallStats = processAggregatableFieldsExistResponse( - aggregatableOverallStatsResp, - aggregatableFields, - samplerShardSize, - totalCount - ); + const totalCount = documentCountStats?.totalCount ?? 0; - const nonAggregatableOverallStats = processNonAggregatableFieldsExistResponse( - nonAggregatableOverallStatsResp, - nonAggregatableFields - ); + const aggregatableOverallStats = processAggregatableFieldsExistResponse( + aggregatableOverallStatsResp, + aggregatableFields + ); - setOverallStats({ - documentCountStats, - ...nonAggregatableOverallStats, - ...aggregatableOverallStats, - totalCount, - }); - }, - error: (error) => { - displayError(toasts, searchStrategyParams.index, extractErrorProperties(error)); - setFetchState({ - isRunning: false, - error, - }); - }, - complete: () => { - setFetchState({ - loaded: 100, - isRunning: false, - }); - }, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps + const nonAggregatableOverallStats = processNonAggregatableFieldsExistResponse( + nonAggregatableOverallStatsResp, + nonAggregatableFields + ); + + setOverallStats({ + documentCountStats, + ...nonAggregatableOverallStats, + ...aggregatableOverallStats, + totalCount, + }); + }, + error: (error) => { + displayError(toasts, searchStrategyParams.index, extractErrorProperties(error)); + setFetchState({ + isRunning: false, + error, + }); + }, + complete: () => { + setFetchState({ + loaded: 100, + isRunning: false, + }); + }, + }); + } catch (error) { + // An `AbortError` gets triggered when a user cancels a request by navigating away, we need to ignore these errors. + if (error.name !== 'AbortError') { + displayError(toasts, searchStrategyParams!.index, extractErrorProperties(error)); + } + } }, [data.search, searchStrategyParams, toasts, lastRefresh, probability]); const cancelFetch = useCallback(() => { @@ -286,8 +281,11 @@ export function useOverallStats { startFetch(); + }, [startFetch]); + + useEffect(() => { return cancelFetch; - }, [startFetch, cancelFetch]); + }, [cancelFetch]); return useMemo( () => ({ diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/build_random_sampler_agg.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/build_random_sampler_agg.ts new file mode 100644 index 00000000000000..424da1b9217858 --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/build_random_sampler_agg.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + Aggs, + SamplingOption, + isNormalSamplingOption, + isRandomSamplingOption, +} from '../../../../../common/types/field_stats'; + +export function buildAggregationWithSamplingOption( + aggs: Aggs, + samplingOption: SamplingOption +): Record { + if (!samplingOption) { + return aggs; + } + const { seed } = samplingOption; + + if (isNormalSamplingOption(samplingOption)) { + return { + sample: { + sampler: { + shard_size: samplingOption.shardSize, + }, + aggs, + }, + }; + } + + if (isRandomSamplingOption(samplingOption)) { + return { + sample: { + // @ts-expect-error AggregationsAggregationContainer needs to be updated with random_sampler + random_sampler: { + probability: samplingOption.probability, + ...(seed ? { seed } : {}), + }, + aggs, + }, + }; + } + + // Else, if no sampling, use random sampler with probability set to 1 + // this is so that all results are returned under 'sample' path + return { + sample: { + aggs, + // @ts-expect-error AggregationsAggregationContainer needs to be updated with random_sampler + random_sampler: { + probability: 1, + ...(seed ? { seed } : {}), + }, + }, + }; +} + +/** + * Wraps the supplied aggregations in a random sampler aggregation. + */ +export function buildRandomSamplerAggregation( + aggs: Aggs, + probability: number | null, + seed: number +): Record { + if (probability === null || probability <= 0 || probability > 1) { + return aggs; + } + + return { + sample: { + aggs, + // @ts-expect-error AggregationsAggregationContainer needs to be updated with random_sampler + random_sampler: { + probability, + ...(seed ? { seed } : {}), + }, + }, + }; +} + +export function buildSamplerAggregation( + aggs: Aggs, + shardSize: number +): Record { + if (shardSize <= 0) { + return aggs; + } + + return { + sample: { + aggs, + sampler: { + shard_size: shardSize, + }, + }, + }; +} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_boolean_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_boolean_field_stats.ts index 5b91d3716ffd94..bf941e09526574 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_boolean_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_boolean_field_stats.ts @@ -14,9 +14,10 @@ import type { ISearchOptions, ISearchStart, } from '@kbn/data-plugin/public'; -import { buildSamplerAggregation, getSamplerAggregationsResponsePath } from '@kbn/ml-agg-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { processTopValues } from './utils'; +import { buildAggregationWithSamplingOption } from './build_random_sampler_agg'; import type { Field, BooleanFieldStats, @@ -30,7 +31,7 @@ export const getBooleanFieldsStatsRequest = ( params: FieldStatsCommonRequestParams, fields: Field[] ) => { - const { index, query, runtimeFieldMap, samplerShardSize } = params; + const { index, query, runtimeFieldMap } = params; const size = 0; const aggs: Aggs = {}; @@ -48,7 +49,7 @@ export const getBooleanFieldsStatsRequest = ( }); const searchBody = { query, - aggs: buildSamplerAggregation(aggs, samplerShardSize), + aggs: buildAggregationWithSamplingOption(aggs, params.samplingOption), ...(isPopulatedObject(runtimeFieldMap) ? { runtime_mappings: runtimeFieldMap } : {}), }; @@ -65,7 +66,6 @@ export const fetchBooleanFieldsStats = ( fields: Field[], options: ISearchOptions ): Observable => { - const { samplerShardSize } = params; const request: estypes.SearchRequest = getBooleanFieldsStatsRequest(params, fields); return dataSearch .search({ params: request }, options) @@ -80,15 +80,34 @@ export const fetchBooleanFieldsStats = ( if (!isIKibanaSearchResponse(resp)) return resp; const aggregations = resp.rawResponse.aggregations; - const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); + const aggsPath = ['sample']; + const sampleCount = get(aggregations, [...aggsPath, 'doc_count'], 0); const batchStats: BooleanFieldStats[] = fields.map((field, i) => { const safeFieldName = field.fieldName; + // Sampler agg will yield doc_count that's bigger than the actual # of sampled records + // because it uses the stored _doc_count if available + // https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-doc-count-field.html + // therefore we need to correct it by multiplying by the sampled probability + const count = get( + aggregations, + [...aggsPath, `${safeFieldName}_value_count`, 'doc_count'], + 0 + ); + + const fieldAgg = get(aggregations, [...aggsPath, `${safeFieldName}_values`], {}); + const { topValuesSampleSize, topValues } = processTopValues(fieldAgg); + + const multiplier = + count > sampleCount ? get(aggregations, [...aggsPath, 'probability'], 1) : 1; + const stats: BooleanFieldStats = { fieldName: field.fieldName, - count: get(aggregations, [...aggsPath, `${safeFieldName}_value_count`, 'doc_count'], 0), + count: count * multiplier, trueCount: 0, falseCount: 0, + topValues, + topValuesSampleSize, }; const valueBuckets: Array<{ [key: string]: number }> = get( @@ -97,7 +116,7 @@ export const fetchBooleanFieldsStats = ( [] ); valueBuckets.forEach((bucket) => { - stats[`${bucket.key_as_string}Count`] = bucket.doc_count; + stats[`${bucket.key_as_string}Count` as 'trueCount' | 'falseCount'] = bucket.doc_count; }); return stats; }); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_date_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_date_field_stats.ts index 1f55f8117c1bed..863cd6885fe885 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_date_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_date_field_stats.ts @@ -15,8 +15,8 @@ import type { ISearchOptions, ISearchStart, } from '@kbn/data-plugin/public'; -import { buildSamplerAggregation, getSamplerAggregationsResponsePath } from '@kbn/ml-agg-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { buildAggregationWithSamplingOption } from './build_random_sampler_agg'; import type { FieldStatsCommonRequestParams } from '../../../../../common/types/field_stats'; import type { Field, DateFieldStats, Aggs } from '../../../../../common/types/field_stats'; import { FieldStatsError, isIKibanaSearchResponse } from '../../../../../common/types/field_stats'; @@ -26,7 +26,7 @@ export const getDateFieldsStatsRequest = ( params: FieldStatsCommonRequestParams, fields: Field[] ) => { - const { index, query, runtimeFieldMap, samplerShardSize } = params; + const { index, query, runtimeFieldMap } = params; const size = 0; @@ -45,7 +45,7 @@ export const getDateFieldsStatsRequest = ( const searchBody = { query, - aggs: buildSamplerAggregation(aggs, samplerShardSize), + aggs: buildAggregationWithSamplingOption(aggs, params.samplingOption), ...(isPopulatedObject(runtimeFieldMap) ? { runtime_mappings: runtimeFieldMap } : {}), }; return { @@ -61,8 +61,6 @@ export const fetchDateFieldsStats = ( fields: Field[], options: ISearchOptions ): Observable => { - const { samplerShardSize } = params; - const request: estypes.SearchRequest = getDateFieldsStatsRequest(params, fields); return dataSearch .search({ params: request }, options) @@ -76,15 +74,10 @@ export const fetchDateFieldsStats = ( map((resp) => { if (!isIKibanaSearchResponse(resp)) return resp; const aggregations = resp.rawResponse.aggregations; - const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); + const aggsPath = ['sample']; const batchStats: DateFieldStats[] = fields.map((field, i) => { const safeFieldName = field.safeFieldName; - const docCount = get( - aggregations, - [...aggsPath, `${safeFieldName}_field_stats`, 'doc_count'], - 0 - ); const fieldStatsResp = get( aggregations, [...aggsPath, `${safeFieldName}_field_stats`, 'actual_stats'], @@ -92,7 +85,6 @@ export const fetchDateFieldsStats = ( ); return { fieldName: field.fieldName, - count: docCount, earliest: get(fieldStatsResp, 'min', 0), latest: get(fieldStatsResp, 'max', 0), } as DateFieldStats; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts index 7b2ef96ba2b720..4b6f5bf937b07b 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts @@ -19,6 +19,8 @@ import type { } from '../../../../../common/types/field_stats'; const MINIMUM_RANDOM_SAMPLER_DOC_COUNT = 100000; +const DEFAULT_INITIAL_RANDOM_SAMPLER_PROBABILITY = 0.000001; + export const getDocumentCountStatsRequest = (params: OverallStatsSearchStrategyParams) => { const { index, @@ -69,11 +71,11 @@ export const getDocumentCountStats = async ( search: DataPublicPluginStart['search'], params: OverallStatsSearchStrategyParams, searchOptions: ISearchOptions, - browserSessionSeed: number, + browserSessionSeed: string, probability?: number | null, minimumRandomSamplerDocCount?: number ): Promise => { - const seed = browserSessionSeed ?? Math.abs(seedrandom().int32()); + const seed = browserSessionSeed ?? Math.abs(seedrandom().int32()).toString(); const { index, @@ -83,10 +85,11 @@ export const getDocumentCountStats = async ( runtimeFieldMap, searchQuery, intervalMs, - fieldsToFetch, } = params; - const result = { randomlySampled: false, took: 0, totalCount: 0 }; + // Probability = 1 represents no sampling + const result = { randomlySampled: false, took: 0, totalCount: 0, probability: 1 }; + const filterCriteria = buildBaseFilterCriteria(timeFieldName, earliestMs, latestMs, searchQuery); const query = { @@ -109,7 +112,7 @@ export const getDocumentCountStats = async ( // If probability is provided, use that // Else, make an initial query using very low p // so that we can calculate the next p value that's appropriate for the data set - const initialDefaultProbability = probability ?? 0.000001; + const initialDefaultProbability = probability ?? DEFAULT_INITIAL_RANDOM_SAMPLER_PROBABILITY; const getAggsWithRandomSampling = (p: number) => ({ sampler: { @@ -121,16 +124,13 @@ export const getDocumentCountStats = async ( }, }); + const hasTimeField = timeFieldName !== undefined && intervalMs !== undefined && intervalMs > 0; + const getSearchParams = (aggregations: unknown, trackTotalHits = false) => ({ index, body: { query, - ...(!fieldsToFetch && - timeFieldName !== undefined && - intervalMs !== undefined && - intervalMs > 0 - ? { aggs: aggregations } - : {}), + ...(hasTimeField ? { aggs: aggregations } : {}), ...(isPopulatedObject(runtimeFieldMap) ? { runtime_mappings: runtimeFieldMap } : {}), }, track_total_hits: trackTotalHits, @@ -142,7 +142,7 @@ export const getDocumentCountStats = async ( params: getSearchParams( getAggsWithRandomSampling(initialDefaultProbability), // Track total hits if time field is not defined - timeFieldName === undefined + !hasTimeField ), }, searchOptions @@ -189,13 +189,10 @@ export const getDocumentCountStats = async ( const newProbability = (initialDefaultProbability * numDocs) / (numSampled - 2 * Math.sqrt(numSampled)); - // If the number of docs sampled is indicative of query with < 10 million docs - // proceed to make a vanilla aggregation without any sampling - if ( - numSampled === 0 || - newProbability === Infinity || - numSampled / initialDefaultProbability < 1e7 - ) { + // If the number of docs is < 3 million + // proceed to make a vanilla aggregation without any sampling (probability = 1) + // Minimum of 4 docs (3e6 * 0.000001 + 1) sampled gives us 90% confidence interval # docs is within + if (newProbability === Infinity || numSampled <= 4) { const vanillaAggResp = await search .search( { @@ -241,7 +238,7 @@ export const processDocumentCountStats = ( body: estypes.SearchResponse | undefined, params: OverallStatsSearchStrategyParams, randomlySampled = false -): DocumentCountStats | undefined => { +): Omit | undefined => { if (!body) return undefined; let totalCount = 0; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_numeric_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_numeric_field_stats.ts index 024fbb3577a483..7e016d40cdd395 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_numeric_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_numeric_field_stats.ts @@ -16,30 +16,33 @@ import { ISearchOptions, } from '@kbn/data-plugin/common'; import type { ISearchStart } from '@kbn/data-plugin/public'; -import { buildSamplerAggregation, getSamplerAggregationsResponsePath } from '@kbn/ml-agg-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { - MAX_PERCENT, - PERCENTILE_SPACING, - SAMPLER_TOP_TERMS_SHARD_SIZE, - SAMPLER_TOP_TERMS_THRESHOLD, -} from './constants'; -import type { Aggs, FieldStatsCommonRequestParams } from '../../../../../common/types/field_stats'; +import { processTopValues } from './utils'; +import { isDefined } from '../../../common/util/is_defined'; +import { buildAggregationWithSamplingOption } from './build_random_sampler_agg'; +import { MAX_PERCENT, PERCENTILE_SPACING, SAMPLER_TOP_TERMS_THRESHOLD } from './constants'; +import type { + Aggs, + Bucket, + FieldStatsCommonRequestParams, +} from '../../../../../common/types/field_stats'; import type { Field, NumericFieldStats, - Bucket, FieldStatsError, } from '../../../../../common/types/field_stats'; import { processDistributionData } from '../../utils/process_distribution_data'; import { extractErrorProperties } from '../../utils/error_utils'; -import { isIKibanaSearchResponse } from '../../../../../common/types/field_stats'; +import { + isIKibanaSearchResponse, + isNormalSamplingOption, +} from '../../../../../common/types/field_stats'; export const getNumericFieldsStatsRequest = ( params: FieldStatsCommonRequestParams, fields: Field[] ) => { - const { index, query, runtimeFieldMap, samplerShardSize } = params; + const { index, query, runtimeFieldMap } = params; const size = 0; @@ -83,23 +86,12 @@ export const getNumericFieldsStatsRequest = ( } as AggregationsTermsAggregation, }; - // If cardinality >= SAMPLE_TOP_TERMS_THRESHOLD, run the top terms aggregation - // in a sampler aggregation, even if no sampling has been specified (samplerShardSize < 1). - if (samplerShardSize < 1 && field.cardinality >= SAMPLER_TOP_TERMS_THRESHOLD) { - aggs[`${safeFieldName}_top`] = buildSamplerAggregation( - { - top, - }, - 0.05 - ); - } else { - aggs[`${safeFieldName}_top`] = top; - } + aggs[`${safeFieldName}_top`] = top; }); const searchBody = { query, - aggs: buildSamplerAggregation(aggs, samplerShardSize), + aggs: buildAggregationWithSamplingOption(aggs, params.samplingOption), ...(isPopulatedObject(runtimeFieldMap) ? { runtime_mappings: runtimeFieldMap } : {}), }; @@ -132,7 +124,7 @@ export const fetchNumericFieldsStats = ( if (!isIKibanaSearchResponse(resp)) return resp; const aggregations = resp.rawResponse.aggregations; - const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); + const aggsPath = ['sample']; const batchStats: NumericFieldStats[] = []; @@ -154,28 +146,23 @@ export const fetchNumericFieldsStats = ( topAggsPath.push('top'); } - const topValues: Bucket[] = get(aggregations, [...topAggsPath, 'buckets'], []); + const fieldAgg = get(aggregations, [...topAggsPath], {}) as { buckets: Bucket[] }; + const { topValuesSampleSize, topValues } = processTopValues(fieldAgg); const stats: NumericFieldStats = { fieldName: field.fieldName, - count: docCount, min: get(fieldStatsResp, 'min', 0), max: get(fieldStatsResp, 'max', 0), avg: get(fieldStatsResp, 'avg', 0), isTopValuesSampled: - field.cardinality >= SAMPLER_TOP_TERMS_THRESHOLD || samplerShardSize > 0, + isNormalSamplingOption(params.samplingOption) || + (isDefined(params.samplingProbability) && params.samplingProbability < 1), topValues, - topValuesSampleSize: topValues.reduce( - (acc, curr) => acc + curr.doc_count, - get(aggregations, [...topAggsPath, 'sum_other_doc_count'], 0) - ), - topValuesSamplerShardSize: - field.cardinality >= SAMPLER_TOP_TERMS_THRESHOLD - ? SAMPLER_TOP_TERMS_SHARD_SIZE - : samplerShardSize, + topValuesSampleSize, + topValuesSamplerShardSize: get(aggregations, ['sample', 'doc_count']), }; - if (stats.count > 0) { + if (docCount > 0) { const percentiles = get( aggregations, [...aggsPath, `${safeFieldName}_percentiles`, 'values'], diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_string_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_string_field_stats.ts index 60306ded5d8f4e..a035842fa87675 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_string_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_string_field_stats.ts @@ -15,12 +15,12 @@ import type { ISearchOptions, ISearchStart, } from '@kbn/data-plugin/public'; -import { buildSamplerAggregation, getSamplerAggregationsResponsePath } from '@kbn/ml-agg-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { SAMPLER_TOP_TERMS_SHARD_SIZE, SAMPLER_TOP_TERMS_THRESHOLD } from './constants'; +import { processTopValues } from './utils'; +import { buildAggregationWithSamplingOption } from './build_random_sampler_agg'; +import { SAMPLER_TOP_TERMS_THRESHOLD } from './constants'; import type { Aggs, - Bucket, Field, FieldStatsCommonRequestParams, StringFieldStats, @@ -32,7 +32,7 @@ export const getStringFieldStatsRequest = ( params: FieldStatsCommonRequestParams, fields: Field[] ) => { - const { index, query, runtimeFieldMap, samplerShardSize } = params; + const { index, query, runtimeFieldMap } = params; const size = 0; @@ -49,25 +49,12 @@ export const getStringFieldStatsRequest = ( } as AggregationsTermsAggregation, }; - // If cardinality >= SAMPLE_TOP_TERMS_THRESHOLD, run the top terms aggregation - // in a sampler aggregation, even if no sampling has been specified (samplerShardSize < 1). - if (samplerShardSize < 1 && field.cardinality >= SAMPLER_TOP_TERMS_THRESHOLD) { - aggs[`${safeFieldName}_top`] = { - sampler: { - shard_size: SAMPLER_TOP_TERMS_SHARD_SIZE, - }, - aggs: { - top, - }, - }; - } else { - aggs[`${safeFieldName}_top`] = top; - } + aggs[`${safeFieldName}_top`] = top; }); const searchBody = { query, - aggs: buildSamplerAggregation(aggs, samplerShardSize), + aggs: buildAggregationWithSamplingOption(aggs, params.samplingOption), ...(isPopulatedObject(runtimeFieldMap) ? { runtime_mappings: runtimeFieldMap } : {}), }; @@ -99,7 +86,8 @@ export const fetchStringFieldsStats = ( map((resp) => { if (!isIKibanaSearchResponse(resp)) return resp; const aggregations = resp.rawResponse.aggregations; - const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); + + const aggsPath = ['sample']; const batchStats: StringFieldStats[] = []; fields.forEach((field, i) => { @@ -110,21 +98,18 @@ export const fetchStringFieldsStats = ( topAggsPath.push('top'); } - const topValues: Bucket[] = get(aggregations, [...topAggsPath, 'buckets'], []); + const fieldAgg = get(aggregations, [...topAggsPath], {}); + const { topValuesSampleSize, topValues } = processTopValues( + fieldAgg, + get(aggregations, ['sample', 'doc_count']) + ); const stats = { fieldName: field.fieldName, - isTopValuesSampled: - field.cardinality >= SAMPLER_TOP_TERMS_THRESHOLD || samplerShardSize > 0, + isTopValuesSampled: true, topValues, - topValuesSampleSize: topValues.reduce( - (acc, curr) => acc + curr.doc_count, - get(aggregations, [...topAggsPath, 'sum_other_doc_count'], 0) - ), - topValuesSamplerShardSize: - field.cardinality >= SAMPLER_TOP_TERMS_THRESHOLD - ? SAMPLER_TOP_TERMS_SHARD_SIZE - : samplerShardSize, + topValuesSampleSize, + topValuesSamplerShardSize: get(aggregations, ['sample', 'doc_count']), }; batchStats.push(stats); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/overall_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/overall_stats.ts index fcc9855cf59b6f..7c0c4ccb9c498a 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/overall_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/overall_stats.ts @@ -10,21 +10,21 @@ import { get } from 'lodash'; import { Query } from '@kbn/es-query'; import { IKibanaSearchResponse } from '@kbn/data-plugin/common'; import type { AggCardinality } from '@kbn/ml-agg-utils'; -import { buildSamplerAggregation, getSamplerAggregationsResponsePath } from '@kbn/ml-agg-utils'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { buildAggregationWithSamplingOption } from './build_random_sampler_agg'; import { buildBaseFilterCriteria, getSafeAggregationName, } from '../../../../../common/utils/query_utils'; import { getDatafeedAggregations } from '../../../../../common/utils/datafeed_utils'; import { AggregatableField, NonAggregatableField } from '../../types/overall_stats'; -import { Aggs } from '../../../../../common/types/field_stats'; +import { Aggs, SamplingOption } from '../../../../../common/types/field_stats'; export const checkAggregatableFieldsExistRequest = ( dataViewTitle: string, query: Query['query'], aggregatableFields: string[], - samplerShardSize: number, + samplingOption: SamplingOption, timeFieldName: string | undefined, earliestMs?: number, latestMs?: number, @@ -73,7 +73,9 @@ export const checkAggregatableFieldsExistRequest = ( filter: filterCriteria, }, }, - ...(isPopulatedObject(aggs) ? { aggs: buildSamplerAggregation(aggs, samplerShardSize) } : {}), + ...(isPopulatedObject(aggs) + ? { aggs: buildAggregationWithSamplingOption(aggs, samplingOption) } + : {}), ...(isPopulatedObject(combinedRuntimeMappings) ? { runtime_mappings: combinedRuntimeMappings } : {}), @@ -109,8 +111,6 @@ export function isNonAggregatableFieldOverallStats( export const processAggregatableFieldsExistResponse = ( responses: AggregatableFieldOverallStats[] | undefined, aggregatableFields: string[], - samplerShardSize: number, - totalCount: number, datafeedConfig?: estypes.MlDatafeed ) => { const stats = { @@ -123,12 +123,17 @@ export const processAggregatableFieldsExistResponse = ( responses.forEach(({ rawResponse: body, aggregatableFields: aggregatableFieldsChunk }) => { const aggregations = body.aggregations; - const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); - const sampleCount = - samplerShardSize > 0 ? get(aggregations, ['sample', 'doc_count'], 0) : totalCount; + const aggsPath = ['sample']; + const sampleCount = aggregations.sample.doc_count; aggregatableFieldsChunk.forEach((field, i) => { const safeFieldName = getSafeAggregationName(field, i); + // Sampler agg will yield doc_count that's bigger than the actual # of sampled records + // because it uses the stored _doc_count if available + // https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-doc-count-field.html + // therefore we need to correct it by multiplying by the sampled probability const count = get(aggregations, [...aggsPath, `${safeFieldName}_count`, 'doc_count'], 0); + const multiplier = + count > sampleCount ? get(aggregations, [...aggsPath, 'probability'], 1) : 1; if (count > 0) { const cardinality = get( aggregations, @@ -140,7 +145,7 @@ export const processAggregatableFieldsExistResponse = ( existsInDocs: true, stats: { sampleCount, - count, + count: count * multiplier, cardinality, }, }); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/utils.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/utils.ts new file mode 100644 index 00000000000000..5da647fa1d7e9b --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/utils.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 { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { Bucket } from '../../../../../common/types/field_stats'; + +/** Utility to calculate the correct sample size, whether or not _doc_count is set + * and calculate the percentage (in fraction) for each bucket + * https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-doc-count-field.html + * @param aggResult + */ +export const processTopValues = (aggResult: object, sampledCount?: number) => { + const topValuesBuckets: Bucket[] = isPopulatedObject<'buckets', Bucket[]>(aggResult, ['buckets']) + ? aggResult.buckets + : []; + const sumOtherDocCount = isPopulatedObject<'sum_other_doc_count', number>(aggResult, [ + 'sum_other_doc_count', + ]) + ? aggResult.sum_other_doc_count + : 0; + const valuesInTopBuckets = + topValuesBuckets?.reduce((prev, bucket) => bucket.doc_count + prev, 0) || 0; + // We could use `aggregations.sample.sample_count.value` instead, but it does not always give a correct sum + // See Github issue #144625 + const realNumberOfDocuments = valuesInTopBuckets + sumOtherDocCount; + const topValues = topValuesBuckets.map((bucket) => ({ + ...bucket, + doc_count: sampledCount + ? Math.floor(bucket.doc_count * (sampledCount / realNumberOfDocuments)) + : bucket.doc_count, + percent: bucket.doc_count / realNumberOfDocuments, + })); + + return { + topValuesSampleSize: realNumberOfDocuments, + topValues, + }; +}; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts index cb3e465683f814..2baba961691fbf 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts @@ -9,7 +9,6 @@ import type { Filter } from '@kbn/es-query'; import type { Query } from '@kbn/data-plugin/common/query'; import type { RandomSamplerOption } from '../constants/random_sampler'; import type { SearchQueryLanguage } from './combined_query'; - export interface ListingPageUrlState { pageSize: number; pageIndex: number; diff --git a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts index 9b8b9a7c41039f..a99a7a4bfc80be 100644 --- a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts +++ b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts @@ -5,19 +5,27 @@ * 2.0. */ -import { IngestSetProcessor, MlTrainedModelConfig } from '@elastic/elasticsearch/lib/api/types'; +import { + IngestRemoveProcessor, + IngestSetProcessor, + MlTrainedModelConfig, + MlTrainedModelStats, +} from '@elastic/elasticsearch/lib/api/types'; import { BUILT_IN_MODEL_TAG } from '@kbn/ml-plugin/common/constants/data_frame_analytics'; import { SUPPORTED_PYTORCH_TASKS } from '@kbn/ml-plugin/common/constants/trained_models'; -import { MlInferencePipeline } from '../types/pipelines'; +import { MlInferencePipeline, TrainedModelState } from '../types/pipelines'; import { BUILT_IN_MODEL_TAG as LOCAL_BUILT_IN_MODEL_TAG, generateMlInferencePipelineBody, getMlModelTypesForModelConfig, + getRemoveProcessorForInferenceType, getSetProcessorForInferenceType, - SUPPORTED_PYTORCH_TASKS as LOCAL_SUPPORTED_PYTORCH_TASKS, parseMlInferenceParametersFromPipeline, + parseModelStateFromStats, + parseModelStateReasonFromStats, + SUPPORTED_PYTORCH_TASKS as LOCAL_SUPPORTED_PYTORCH_TASKS, } from '.'; const mockModel: MlTrainedModelConfig = { @@ -63,6 +71,38 @@ describe('getMlModelTypesForModelConfig lib function', () => { }); }); +describe('getRemoveProcessorForInferenceType lib function', () => { + const destinationField = 'dest'; + + it('should return expected value for TEXT_CLASSIFICATION', () => { + const inferenceType = SUPPORTED_PYTORCH_TASKS.TEXT_CLASSIFICATION; + + const expected: IngestRemoveProcessor = { + field: destinationField, + ignore_missing: true, + }; + + expect(getRemoveProcessorForInferenceType(destinationField, inferenceType)).toEqual(expected); + }); + + it('should return expected value for TEXT_EMBEDDING', () => { + const inferenceType = SUPPORTED_PYTORCH_TASKS.TEXT_EMBEDDING; + + const expected: IngestRemoveProcessor = { + field: destinationField, + ignore_missing: true, + }; + + expect(getRemoveProcessorForInferenceType(destinationField, inferenceType)).toEqual(expected); + }); + + it('should return undefined for unknown inferenceType', () => { + const inferenceType = 'wrongInferenceType'; + + expect(getRemoveProcessorForInferenceType(destinationField, inferenceType)).toBeUndefined(); + }); +}); + describe('getSetProcessorForInferenceType lib function', () => { const destinationField = 'dest'; @@ -78,7 +118,7 @@ describe('getSetProcessorForInferenceType lib function', () => { description: "Copy the predicted_value to 'dest' if the prediction_probability is greater than 0.5", field: destinationField, - if: 'ctx.ml.inference.dest.prediction_probability > 0.5', + if: "ctx?.ml?.inference != null && ctx.ml.inference['dest'] != null && ctx.ml.inference['dest'].prediction_probability > 0.5", value: undefined, }; @@ -92,6 +132,7 @@ describe('getSetProcessorForInferenceType lib function', () => { copy_from: 'ml.inference.dest.predicted_value', description: "Copy the predicted_value to 'dest'", field: destinationField, + if: "ctx?.ml?.inference != null && ctx.ml.inference['dest'] != null", value: undefined, }; @@ -185,13 +226,19 @@ describe('generateMlInferencePipelineBody lib function', () => { expect.objectContaining({ description: expect.any(String), processors: expect.arrayContaining([ + expect.objectContaining({ + remove: { + field: 'my-destination-field', + ignore_missing: true, + }, + }), expect.objectContaining({ set: { copy_from: 'ml.inference.my-destination-field.predicted_value', description: "Copy the predicted_value to 'my-destination-field' if the prediction_probability is greater than 0.5", field: 'my-destination-field', - if: 'ctx.ml.inference.my-destination-field.prediction_probability > 0.5', + if: "ctx?.ml?.inference != null && ctx.ml.inference['my-destination-field'] != null && ctx.ml.inference['my-destination-field'].prediction_probability > 0.5", }, }), ]), @@ -241,3 +288,80 @@ describe('parseMlInferenceParametersFromPipeline', () => { ).toBeNull(); }); }); + +describe('parseModelStateFromStats', () => { + it('returns not deployed for undefined stats', () => { + expect(parseModelStateFromStats()).toEqual(TrainedModelState.NotDeployed); + }); + it('returns Started', () => { + expect( + parseModelStateFromStats({ + deployment_stats: { + state: 'started', + }, + } as unknown as MlTrainedModelStats) + ).toEqual(TrainedModelState.Started); + }); + it('returns Starting', () => { + expect( + parseModelStateFromStats({ + deployment_stats: { + state: 'starting', + }, + } as unknown as MlTrainedModelStats) + ).toEqual(TrainedModelState.Starting); + }); + it('returns Stopping', () => { + expect( + parseModelStateFromStats({ + deployment_stats: { + state: 'stopping', + }, + } as unknown as MlTrainedModelStats) + ).toEqual(TrainedModelState.Stopping); + }); + it('returns Failed', () => { + expect( + parseModelStateFromStats({ + deployment_stats: { + state: 'failed', + }, + } as unknown as MlTrainedModelStats) + ).toEqual(TrainedModelState.Failed); + }); + it('returns not deployed if an unknown state is received', () => { + expect( + parseModelStateFromStats({ + deployment_stats: { + state: 'other thing', + }, + } as unknown as MlTrainedModelStats) + ).toEqual(TrainedModelState.NotDeployed); + }); +}); + +describe('parseModelStateReasonFromStats', () => { + it('returns reason from deployment_stats', () => { + const reason = 'This is the reason the model is in a failed state'; + expect( + parseModelStateReasonFromStats({ + deployment_stats: { + reason, + state: 'failed', + }, + } as unknown as MlTrainedModelStats) + ).toEqual(reason); + }); + it('returns undefined if reason not found from deployment_stats', () => { + expect( + parseModelStateReasonFromStats({ + deployment_stats: { + state: 'failed', + }, + } as unknown as MlTrainedModelStats) + ).toBeUndefined(); + }); + it('returns undefined stats is undefined', () => { + expect(parseModelStateReasonFromStats(undefined)).toBeUndefined(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts index 9774d0e4f4fe18..61669d36badadf 100644 --- a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts +++ b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts @@ -7,11 +7,17 @@ import { IngestPipeline, + IngestRemoveProcessor, IngestSetProcessor, MlTrainedModelConfig, + MlTrainedModelStats, } from '@elastic/elasticsearch/lib/api/types'; -import { MlInferencePipeline, CreateMlInferencePipelineParameters } from '../types/pipelines'; +import { + MlInferencePipeline, + CreateMlInferencePipelineParameters, + TrainedModelState, +} from '../types/pipelines'; // Getting an error importing this from @kbn/ml-plugin/common/constants/data_frame_analytics' // So defining it locally for now with a test to make sure it matches. @@ -53,6 +59,7 @@ export const generateMlInferencePipelineBody = ({ model.input?.field_names?.length > 0 ? model.input.field_names[0] : 'MODEL_INPUT_FIELD'; const inferenceType = Object.keys(model.inference_config)[0]; + const remove = getRemoveProcessorForInferenceType(destinationField, inferenceType); const set = getSetProcessorForInferenceType(destinationField, inferenceType); return { @@ -64,6 +71,7 @@ export const generateMlInferencePipelineBody = ({ ignore_missing: true, }, }, + ...(remove ? [{ remove }] : []), { inference: { field_map: { @@ -118,7 +126,7 @@ export const getSetProcessorForInferenceType = ( copy_from: `${prefixedDestinationField}.predicted_value`, description: `Copy the predicted_value to '${destinationField}' if the prediction_probability is greater than 0.5`, field: destinationField, - if: `ctx.${prefixedDestinationField}.prediction_probability > 0.5`, + if: `ctx?.ml?.inference != null && ctx.ml.inference['${destinationField}'] != null && ctx.ml.inference['${destinationField}'].prediction_probability > 0.5`, value: undefined, }; } else if (inferenceType === SUPPORTED_PYTORCH_TASKS.TEXT_EMBEDDING) { @@ -126,6 +134,7 @@ export const getSetProcessorForInferenceType = ( copy_from: `${prefixedDestinationField}.predicted_value`, description: `Copy the predicted_value to '${destinationField}'`, field: destinationField, + if: `ctx?.ml?.inference != null && ctx.ml.inference['${destinationField}'] != null`, value: undefined, }; } @@ -133,6 +142,21 @@ export const getSetProcessorForInferenceType = ( return set; }; +export const getRemoveProcessorForInferenceType = ( + destinationField: string, + inferenceType: string +): IngestRemoveProcessor | undefined => { + if ( + inferenceType === SUPPORTED_PYTORCH_TASKS.TEXT_CLASSIFICATION || + inferenceType === SUPPORTED_PYTORCH_TASKS.TEXT_EMBEDDING + ) { + return { + field: destinationField, + ignore_missing: true, + }; + } +}; + /** * Parses model types list from the given configuration of a trained machine learning model * @param trainedModel configuration for a trained machine learning model @@ -177,3 +201,22 @@ export const parseMlInferenceParametersFromPipeline = ( source_field: sourceField, }; }; + +export const parseModelStateFromStats = (trainedModelStats?: Partial) => { + switch (trainedModelStats?.deployment_stats?.state) { + case 'started': + return TrainedModelState.Started; + case 'starting': + return TrainedModelState.Starting; + case 'stopping': + return TrainedModelState.Stopping; + // @ts-ignore: type is wrong, "failed" is a possible state + case 'failed': + return TrainedModelState.Failed; + default: + return TrainedModelState.NotDeployed; + } +}; + +export const parseModelStateReasonFromStats = (trainedModelStats?: Partial) => + trainedModelStats?.deployment_stats?.reason; diff --git a/x-pack/plugins/enterprise_search/common/stats.ts b/x-pack/plugins/enterprise_search/common/stats.ts new file mode 100644 index 00000000000000..6b724f93bb1a59 --- /dev/null +++ b/x-pack/plugins/enterprise_search/common/stats.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface SyncJobsStats { + connected: number; + errors: number; + in_progress: number; + incomplete: number; + long_running: number; + orphaned_jobs: number; +} diff --git a/x-pack/plugins/enterprise_search/common/types/connectors.ts b/x-pack/plugins/enterprise_search/common/types/connectors.ts index 7aeae229a4f40b..5340c6d9b8fd36 100644 --- a/x-pack/plugins/enterprise_search/common/types/connectors.ts +++ b/x-pack/plugins/enterprise_search/common/types/connectors.ts @@ -140,18 +140,23 @@ export interface ConnectorSyncJob { cancelation_requested_at: string | null; canceled_at: string | null; completed_at: string | null; - connector_id: string; + connector: { + configuration: ConnectorConfiguration; + filtering: FilteringRules | null; + id: string; + index_name: string; + language: string; + pipeline: IngestPipelineParams | null; + service_type: string; + }; created_at: string; deleted_document_count: number; error: string | null; - filtering: FilteringRules | null; id: string; - index_name: string; indexed_document_count: number; indexed_document_volume: number; last_seen: string; metadata: Record; - pipeline: IngestPipelineParams | null; started_at: string; status: SyncStatus; trigger_method: TriggerMethod; diff --git a/x-pack/plugins/enterprise_search/common/types/error_codes.ts b/x-pack/plugins/enterprise_search/common/types/error_codes.ts index 79f1d2e69856cd..a92f0025b86026 100644 --- a/x-pack/plugins/enterprise_search/common/types/error_codes.ts +++ b/x-pack/plugins/enterprise_search/common/types/error_codes.ts @@ -11,6 +11,7 @@ export enum ErrorCode { ANALYTICS_COLLECTION_NAME_INVALID = 'analytics_collection_name_invalid', CONNECTOR_DOCUMENT_ALREADY_EXISTS = 'connector_document_already_exists', CRAWLER_ALREADY_EXISTS = 'crawler_already_exists', + DOCUMENT_NOT_FOUND = 'document_not_found', INDEX_ALREADY_EXISTS = 'index_already_exists', INDEX_NOT_FOUND = 'index_not_found', PIPELINE_ALREADY_EXISTS = 'pipeline_already_exists', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/ml_models.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/ml_models.mock.ts new file mode 100644 index 00000000000000..32f23e41bc4ac0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/ml_models.mock.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + MlTrainedModelDeploymentStats, + MlTrainedModelStats, +} from '@elastic/elasticsearch/lib/api/types'; +import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; + +export const nerModel: TrainedModelConfigResponse = { + inference_config: { + ner: { + classification_labels: [ + 'O', + 'B_PER', + 'I_PER', + 'B_ORG', + 'I_ORG', + 'B_LOC', + 'I_LOC', + 'B_MISC', + 'I_MISC', + ], + tokenization: { + bert: { + do_lower_case: false, + max_sequence_length: 512, + span: -1, + truncate: 'first', + with_special_tokens: true, + }, + }, + }, + }, + input: { + field_names: ['text_field'], + }, + model_id: 'ner-mocked-model', + model_type: 'pytorch', + tags: [], + version: '1', +}; + +export const textClassificationModel: TrainedModelConfigResponse = { + inference_config: { + text_classification: { + classification_labels: ['anger', 'disgust', 'fear', 'joy', 'neutral', 'sadness', 'surprise'], + num_top_classes: 0, + tokenization: { + roberta: { + add_prefix_space: false, + do_lower_case: false, + max_sequence_length: 512, + span: -1, + truncate: 'first', + with_special_tokens: true, + }, + }, + }, + }, + input: { + field_names: ['text_field'], + }, + model_id: 'text-classification-mocked-model', + model_type: 'pytorch', + tags: [], + version: '2', +}; + +export const mlModels: TrainedModelConfigResponse[] = [nerModel, textClassificationModel]; + +export const mlModelStats: { + count: number; + trained_model_stats: MlTrainedModelStats[]; +} = { + count: 2, + trained_model_stats: [ + { + model_id: nerModel.model_id, + model_size_stats: { + model_size_bytes: 260831121, + required_native_memory_bytes: 773320482, + }, + pipeline_count: 0, + deployment_stats: { + allocation_status: { + allocation_count: 1, + target_allocation_count: 1, + state: 'fully_allocated', + }, + error_count: 0, + inference_count: 0, + nodes: [], + number_of_allocations: 1, + state: 'started', + threads_per_allocation: 1, + } as unknown as MlTrainedModelDeploymentStats, + }, + { + deployment_stats: { + allocation_status: { + allocation_count: 1, + target_allocation_count: 1, + state: 'fully_allocated', + }, + error_count: 0, + inference_count: 0, + nodes: [], + number_of_allocations: 1, + state: 'started', + threads_per_allocation: 1, + } as unknown as MlTrainedModelDeploymentStats, + model_id: textClassificationModel.model_id, + model_size_stats: { + model_size_bytes: 260831121, + required_native_memory_bytes: 773320482, + }, + pipeline_count: 0, + }, + ], +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/sync_job.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/sync_job.mock.ts index a8504230a494a9..04a3908f5d5b6a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/sync_job.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/sync_job.mock.ts @@ -14,21 +14,26 @@ export const syncJob: ConnectorSyncJob = { cancelation_requested_at: null, canceled_at: null, completed_at: '2022-09-05T15:59:39.816+00:00', - connector_id: 'we2284IBjobuR2-lAuXh', + connector: { + configuration: {}, + filtering: null, + id: 'we2284IBjobuR2-lAuXh', + index_name: 'indexName', + language: '', + pipeline: null, + service_type: '', + }, created_at: '2022-09-05T14:59:39.816+00:00', deleted_document_count: 20, error: null, - filtering: null, id: 'id', - index_name: 'indexName', indexed_document_count: 50, indexed_document_volume: 40, last_seen: '2022-09-05T15:59:39.816+00:00', metadata: {}, - pipeline: null, - trigger_method: TriggerMethod.ON_DEMAND, started_at: '2022-09-05T14:59:39.816+00:00', status: SyncStatus.COMPLETED, + trigger_method: TriggerMethod.ON_DEMAND, worker_hostname: 'hostname_fake', }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/types.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/types.ts index f828d34bdde12d..ce941613e35873 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/types.ts @@ -161,6 +161,12 @@ export interface DomainConfigFromServer { sitemap_urls: string[]; } +export interface CrawlScheduleFromServer { + frequency: number; + unit: CrawlUnits; + use_connector_schedule: boolean; +} + // Client export interface CrawlerDomain { @@ -252,6 +258,7 @@ export type CrawlEvent = CrawlRequest & { export interface CrawlSchedule { frequency: number; unit: CrawlUnits; + useConnectorSchedule: boolean; } export interface DomainConfig { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/utils.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/utils.ts index ab47d8a575c5b8..7886d349044c06 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/utils.ts @@ -29,6 +29,8 @@ import { BasicCrawlerAuth, CrawlerAuth, RawCrawlerAuth, + CrawlScheduleFromServer, + CrawlSchedule, } from './types'; export function crawlerDomainServerToClient(payload: CrawlerDomainFromServer): CrawlerDomain { @@ -241,6 +243,16 @@ export const crawlerDomainsWithMetaServerToClient = ({ meta, }); +export const crawlScheduleServerToClient = ({ + frequency, + unit, + use_connector_schedule: useConnectorSchedule, +}: CrawlScheduleFromServer): CrawlSchedule => ({ + frequency, + unit, + useConnectorSchedule, +}); + export function isBasicCrawlerAuth(auth: CrawlerAuth): auth is BasicCrawlerAuth { return auth !== null && (auth as BasicCrawlerAuth).type === 'basic'; } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/documents/get_document_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/documents/get_document_logic.ts new file mode 100644 index 00000000000000..5f5cf247f57f80 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/documents/get_document_logic.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 { GetResponse } from '@elastic/elasticsearch/lib/api/types'; + +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface GetDocumentsArgs { + documentId: string; + indexName: string; +} + +export type GetDocumentsResponse = GetResponse; + +export const getDocument = async ({ indexName, documentId }: GetDocumentsArgs) => { + const route = `/internal/enterprise_search/indices/${indexName}/document/${documentId}`; + + return await HttpLogic.values.http.get(route); +}; + +export const GetDocumentsApiLogic = createApiLogic(['get_documents_logic'], getDocument); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/documents/get_documents_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/documents/get_documents_logic.test.ts new file mode 100644 index 00000000000000..fb01683fff4cb5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/documents/get_documents_logic.test.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { nextTick } from '@kbn/test-jest-helpers'; + +import { getDocument } from './get_document_logic'; + +describe('getDocumentApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('getDocument', () => { + it('calls correct api', async () => { + const promise = Promise.resolve({ + _id: 'test-id', + _index: 'indexName', + _source: {}, + found: true, + }); + http.get.mockReturnValue(promise); + const result = getDocument({ documentId: '123123', indexName: 'indexName' }); + await nextTick(); + expect(http.get).toHaveBeenCalledWith( + '/internal/enterprise_search/indices/indexName/document/123123' + ); + await expect(result).resolves.toEqual({ + _id: 'test-id', + _index: 'indexName', + _source: {}, + found: true, + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_model_stats_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_model_stats_logic.test.ts new file mode 100644 index 00000000000000..c36704a46604d4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_model_stats_logic.test.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 { mockHttpValues } from '../../../__mocks__/kea_logic'; +import { mlModelStats } from '../../__mocks__/ml_models.mock'; + +import { getMLModelsStats } from './ml_model_stats_logic'; + +describe('MLModelsApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('getMLModelsStats', () => { + it('calls the ml api', async () => { + http.get.mockResolvedValue(mlModelStats); + const result = await getMLModelsStats(); + expect(http.get).toHaveBeenCalledWith('/api/ml/trained_models/_stats'); + expect(result).toEqual(mlModelStats); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_model_stats_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_model_stats_logic.ts new file mode 100644 index 00000000000000..d80c1836a9597b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_model_stats_logic.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 { MlTrainedModelStats } from '@elastic/elasticsearch/lib/api/types'; + +import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export type GetMlModelsStatsArgs = undefined; + +export interface GetMlModelsStatsResponse { + count: number; + trained_model_stats: MlTrainedModelStats[]; +} + +export const getMLModelsStats = async () => { + return await HttpLogic.values.http.get('/api/ml/trained_models/_stats'); +}; + +export const MLModelsStatsApiLogic = createApiLogic( + ['ml_models_stats_api_logic'], + getMLModelsStats +); + +export type MLModelsStatsApiLogicActions = Actions; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.test.ts index 5ba9ef36197a92..6196d6a162dae5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.test.ts @@ -5,8 +5,7 @@ * 2.0. */ import { mockHttpValues } from '../../../__mocks__/kea_logic'; - -import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; +import { mlModels } from '../../__mocks__/ml_models.mock'; import { getMLModels } from './ml_models_logic'; @@ -17,55 +16,12 @@ describe('MLModelsApiLogic', () => { }); describe('getMLModels', () => { it('calls the ml api', async () => { - const response: Promise = Promise.resolve([ - { - inference_config: {}, - input: { - field_names: [], - }, - model_id: 'a-model-001', - model_type: 'pytorch', - tags: ['pytorch', 'ner'], - version: '1', - }, - { - inference_config: {}, - input: { - field_names: [], - }, - model_id: 'a-model-002', - model_type: 'lang_ident', - tags: [], - version: '2', - }, - ]); - http.get.mockReturnValue(response); + http.get.mockResolvedValue(mlModels); const result = await getMLModels(); expect(http.get).toHaveBeenCalledWith('/api/ml/trained_models', { query: { size: 1000, with_pipelines: true }, }); - expect(result).toEqual([ - { - inference_config: {}, - input: { - field_names: [], - }, - model_id: 'a-model-001', - model_type: 'pytorch', - tags: ['pytorch', 'ner'], - version: '1', - }, - { - inference_config: {}, - input: { - field_names: [], - }, - model_id: 'a-model-002', - model_type: 'lang_ident', - tags: [], - version: '2', - }, - ]); + expect(result).toEqual(mlModels); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.ts index a0d86a821afd37..63095f3fbaa2d6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_models_logic.ts @@ -6,7 +6,7 @@ */ import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; -import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; export type GetMlModelsArgs = number | undefined; @@ -20,3 +20,5 @@ export const getMLModels = async (size: GetMlModelsArgs = 1000) => { }; export const MLModelsApiLogic = createApiLogic(['ml_models_api_logic'], getMLModels); + +export type MLModelsApiLogicActions = Actions; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_trained_models_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_trained_models_logic.test.ts new file mode 100644 index 00000000000000..d204b383055afa --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_trained_models_logic.test.ts @@ -0,0 +1,173 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { LogicMounter } from '../../../__mocks__/kea_logic'; +import { mlModels, mlModelStats } from '../../__mocks__/ml_models.mock'; + +import { HttpError, Status } from '../../../../../common/types/api'; + +import { MLModelsStatsApiLogic } from './ml_model_stats_logic'; +import { MLModelsApiLogic } from './ml_models_logic'; +import { TrainedModelsApiLogic, TrainedModelsApiLogicValues } from './ml_trained_models_logic'; + +const DEFAULT_VALUES: TrainedModelsApiLogicValues = { + error: null, + status: Status.IDLE, + data: null, + // models + modelsApiStatus: { + status: Status.IDLE, + }, + modelsData: undefined, + modelsApiError: undefined, + modelsStatus: Status.IDLE, + // stats + modelStatsApiStatus: { + status: Status.IDLE, + }, + modelStatsData: undefined, + modelsStatsApiError: undefined, + modelStatsStatus: Status.IDLE, +}; + +describe('TrainedModelsApiLogic', () => { + const { mount } = new LogicMounter(TrainedModelsApiLogic); + const { mount: mountMLModelsApiLogic } = new LogicMounter(MLModelsApiLogic); + const { mount: mountMLModelsStatsApiLogic } = new LogicMounter(MLModelsStatsApiLogic); + + beforeEach(() => { + jest.clearAllMocks(); + + mountMLModelsApiLogic(); + mountMLModelsStatsApiLogic(); + mount(); + }); + + it('has default values', () => { + expect(TrainedModelsApiLogic.values).toEqual(DEFAULT_VALUES); + }); + describe('selectors', () => { + describe('data', () => { + it('returns combined trained models', () => { + MLModelsApiLogic.actions.apiSuccess(mlModels); + MLModelsStatsApiLogic.actions.apiSuccess(mlModelStats); + + expect(TrainedModelsApiLogic.values.data).toEqual([ + { + ...mlModels[0], + ...mlModelStats.trained_model_stats[0], + }, + { + ...mlModels[1], + ...mlModelStats.trained_model_stats[1], + }, + ]); + }); + it('returns just models if stats not available', () => { + MLModelsApiLogic.actions.apiSuccess(mlModels); + + expect(TrainedModelsApiLogic.values.data).toEqual(mlModels); + }); + it('returns null trained models even with stats if models missing', () => { + MLModelsStatsApiLogic.actions.apiSuccess(mlModelStats); + + expect(TrainedModelsApiLogic.values.data).toEqual(null); + }); + }); + describe('error', () => { + const modelError: HttpError = { + body: { + error: 'Model Error', + statusCode: 400, + }, + fetchOptions: {}, + request: {}, + } as HttpError; + const statsError: HttpError = { + body: { + error: 'Stats Error', + statusCode: 500, + }, + fetchOptions: {}, + request: {}, + } as HttpError; + + it('returns null with no errors', () => { + MLModelsApiLogic.actions.apiSuccess(mlModels); + MLModelsStatsApiLogic.actions.apiSuccess(mlModelStats); + + expect(TrainedModelsApiLogic.values.error).toBeNull(); + }); + it('models error', () => { + MLModelsApiLogic.actions.apiError(modelError); + + expect(TrainedModelsApiLogic.values.error).toBe(modelError); + }); + it('stats error', () => { + MLModelsStatsApiLogic.actions.apiError(statsError); + + expect(TrainedModelsApiLogic.values.error).toBe(statsError); + }); + it('prefers models error if both api calls fail', () => { + MLModelsApiLogic.actions.apiError(modelError); + MLModelsStatsApiLogic.actions.apiError(statsError); + + expect(TrainedModelsApiLogic.values.error).toBe(modelError); + }); + }); + describe('status', () => { + it('returns matching status for both calls', () => { + MLModelsApiLogic.actions.apiSuccess(mlModels); + MLModelsStatsApiLogic.actions.apiSuccess(mlModelStats); + + expect(TrainedModelsApiLogic.values.status).toEqual(Status.SUCCESS); + }); + it('returns models status when its lower', () => { + MLModelsStatsApiLogic.actions.apiSuccess(mlModelStats); + + expect(TrainedModelsApiLogic.values.status).toEqual(Status.IDLE); + }); + it('returns stats status when its lower', () => { + MLModelsApiLogic.actions.apiSuccess(mlModels); + + expect(TrainedModelsApiLogic.values.status).toEqual(Status.IDLE); + }); + it('returns error status if one api call fails', () => { + MLModelsApiLogic.actions.apiSuccess(mlModels); + MLModelsStatsApiLogic.actions.apiError({ + body: { + error: 'Stats Error', + statusCode: 500, + }, + fetchOptions: {}, + request: {}, + } as HttpError); + + expect(TrainedModelsApiLogic.values.status).toEqual(Status.ERROR); + }); + }); + }); + describe('actions', () => { + it('makeRequest fetches models and stats', () => { + jest.spyOn(TrainedModelsApiLogic.actions, 'makeGetModelsRequest'); + jest.spyOn(TrainedModelsApiLogic.actions, 'makeGetModelsStatsRequest'); + + TrainedModelsApiLogic.actions.makeRequest(undefined); + + expect(TrainedModelsApiLogic.actions.makeGetModelsRequest).toHaveBeenCalledTimes(1); + expect(TrainedModelsApiLogic.actions.makeGetModelsStatsRequest).toHaveBeenCalledTimes(1); + }); + it('apiReset resets both api logics', () => { + jest.spyOn(TrainedModelsApiLogic.actions, 'getModelsApiReset'); + jest.spyOn(TrainedModelsApiLogic.actions, 'getModelsStatsApiReset'); + + TrainedModelsApiLogic.actions.apiReset(); + + expect(TrainedModelsApiLogic.actions.getModelsApiReset).toHaveBeenCalledTimes(1); + expect(TrainedModelsApiLogic.actions.getModelsStatsApiReset).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_trained_models_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_trained_models_logic.ts new file mode 100644 index 00000000000000..d36a80df6af6ab --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/ml_trained_models_logic.ts @@ -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 { kea, MakeLogicType } from 'kea'; + +import { MlTrainedModelStats } from '@elastic/elasticsearch/lib/api/types'; +import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; + +import { ApiStatus, Status, HttpError } from '../../../../../common/types/api'; +import { Actions } from '../../../shared/api_logic/create_api_logic'; + +import { + GetMlModelsStatsResponse, + MLModelsStatsApiLogic, + MLModelsStatsApiLogicActions, +} from './ml_model_stats_logic'; +import { GetMlModelsResponse, MLModelsApiLogic, MLModelsApiLogicActions } from './ml_models_logic'; + +export type TrainedModel = TrainedModelConfigResponse & Partial; + +export type TrainedModelsApiLogicActions = Actions & { + getModelsApiError: MLModelsApiLogicActions['apiError']; + getModelsApiReset: MLModelsApiLogicActions['apiReset']; + getModelsApiSuccess: MLModelsApiLogicActions['apiSuccess']; + getModelsStatsApiError: MLModelsStatsApiLogicActions['apiError']; + getModelsStatsApiReset: MLModelsStatsApiLogicActions['apiReset']; + getModelsStatsApiSuccess: MLModelsStatsApiLogicActions['apiSuccess']; + makeGetModelsRequest: MLModelsApiLogicActions['makeRequest']; + makeGetModelsStatsRequest: MLModelsStatsApiLogicActions['makeRequest']; +}; +export interface TrainedModelsApiLogicValues { + error: HttpError | null; + status: Status; + data: TrainedModel[] | null; + // models + modelsApiStatus: ApiStatus; + modelsData: GetMlModelsResponse | undefined; + modelsApiError?: HttpError; + modelsStatus: Status; + // stats + modelStatsApiStatus: ApiStatus; + modelStatsData: GetMlModelsStatsResponse | undefined; + modelsStatsApiError?: HttpError; + modelStatsStatus: Status; +} + +export const TrainedModelsApiLogic = kea< + MakeLogicType +>({ + actions: { + apiError: (error) => error, + apiReset: true, + apiSuccess: (result) => result, + makeRequest: () => undefined, + }, + connect: { + actions: [ + MLModelsApiLogic, + [ + 'apiError as getModelsApiError', + 'apiReset as getModelsApiReset', + 'apiSuccess as getModelsApiSuccess', + 'makeRequest as makeGetModelsRequest', + ], + MLModelsStatsApiLogic, + [ + 'apiError as getModelsStatsApiError', + 'apiReset as getModelsStatsApiReset', + 'apiSuccess as getModelsStatsApiSuccess', + 'makeRequest as makeGetModelsStatsRequest', + ], + ], + values: [ + MLModelsApiLogic, + [ + 'apiStatus as modelsApiStatus', + 'error as modelsApiError', + 'status as modelsStatus', + 'data as modelsData', + ], + MLModelsStatsApiLogic, + [ + 'apiStatus as modelStatsApiStatus', + 'error as modelsStatsApiError', + 'status as modelStatsStatus', + 'data as modelStatsData', + ], + ], + }, + listeners: ({ actions, values }) => ({ + getModelsApiError: (error) => { + actions.apiError(error); + }, + getModelsApiSuccess: () => { + if (!values.data) return; + actions.apiSuccess(values.data); + }, + getModelsStatsApiError: (error) => { + if (values.modelsApiError) return; + actions.apiError(error); + }, + getModelsStatsApiSuccess: () => { + if (!values.data) return; + actions.apiSuccess(values.data); + }, + apiReset: () => { + actions.getModelsApiReset(); + actions.getModelsStatsApiReset(); + }, + makeRequest: () => { + actions.makeGetModelsRequest(undefined); + actions.makeGetModelsStatsRequest(undefined); + }, + }), + path: ['enterprise_search', 'api', 'ml_trained_models_api_logic'], + selectors: ({ selectors }) => ({ + data: [ + () => [selectors.modelsData, selectors.modelStatsData], + ( + modelsData: TrainedModelsApiLogicValues['modelsData'], + modelStatsData: TrainedModelsApiLogicValues['modelStatsData'] + ): TrainedModel[] | null => { + if (!modelsData) return null; + if (!modelStatsData) return modelsData; + const statsMap: Record = + modelStatsData.trained_model_stats.reduce((map, value) => { + if (value.model_id) { + map[value.model_id] = value; + } + return map; + }, {} as Record); + return modelsData.map((modelConfig) => { + const modelStats = statsMap[modelConfig.model_id]; + return { + ...modelConfig, + ...(modelStats ?? {}), + }; + }); + }, + ], + error: [ + () => [selectors.modelsApiStatus, selectors.modelStatsApiStatus], + ( + modelsApiStatus: TrainedModelsApiLogicValues['modelsApiStatus'], + modelStatsApiStatus: TrainedModelsApiLogicValues['modelStatsApiStatus'] + ) => { + if (modelsApiStatus.error) return modelsApiStatus.error; + if (modelStatsApiStatus.error) return modelStatsApiStatus.error; + return null; + }, + ], + status: [ + () => [selectors.modelsApiStatus, selectors.modelStatsApiStatus], + ( + modelsApiStatus: TrainedModelsApiLogicValues['modelsApiStatus'], + modelStatsApiStatus: TrainedModelsApiLogicValues['modelStatsApiStatus'] + ) => { + if (modelsApiStatus.status === modelStatsApiStatus.status) return modelsApiStatus.status; + if (modelsApiStatus.status === Status.ERROR || modelStatsApiStatus.status === Status.ERROR) + return Status.ERROR; + if (modelsApiStatus.status < modelStatsApiStatus.status) return modelsApiStatus.status; + return modelStatsApiStatus.status; + }, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/stats/fetch_sync_jobs_stats_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/stats/fetch_sync_jobs_stats_api_logic.ts new file mode 100644 index 00000000000000..969b1af67406dc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/stats/fetch_sync_jobs_stats_api_logic.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 { SyncJobsStats } from '../../../../../common/stats'; + +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export type FetchSyncJobsStatsResponse = SyncJobsStats; + +export const fetchSyncJobsStats = async () => { + const route = '/internal/enterprise_search/stats/sync_jobs'; + return await HttpLogic.values.http.get(route); +}; + +export const FetchSyncJobsStatsApiLogic = createApiLogic( + ['enterprise_search_content', 'fetch_sync_jobs_stats_api_logic'], + fetchSyncJobsStats +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/constants.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/constants.ts index c7a6b2d576a301..c39d26314671c6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/constants.ts @@ -8,6 +8,8 @@ import { EuiSelectOption } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { languageToText } from '../../utils/language_to_text'; + export const NEW_INDEX_TEMPLATE_TYPES: { [key: string]: string } = { api: i18n.translate('xpack.enterpriseSearch.content.newIndex.types.api', { defaultMessage: 'API endpoint', @@ -45,142 +47,67 @@ export const UNIVERSAL_LANGUAGE_VALUE = ''; export const SUPPORTED_LANGUAGES: EuiSelectOption[] = [ { + text: languageToText(UNIVERSAL_LANGUAGE_VALUE), value: UNIVERSAL_LANGUAGE_VALUE, - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.universalDropDownOptionLabel', - { - defaultMessage: 'Universal', - } - ), }, { - text: '—', disabled: true, + text: '—', }, { + text: languageToText('zh'), value: 'zh', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.chineseDropDownOptionLabel', - { - defaultMessage: 'Chinese', - } - ), }, { + text: languageToText('da'), value: 'da', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.danishDropDownOptionLabel', - { - defaultMessage: 'Danish', - } - ), }, { + text: languageToText('nl'), value: 'nl', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.dutchDropDownOptionLabel', - { - defaultMessage: 'Dutch', - } - ), }, { + text: languageToText('en'), value: 'en', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.englishDropDownOptionLabel', - { - defaultMessage: 'English', - } - ), }, { + text: languageToText('fr'), value: 'fr', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.frenchDropDownOptionLabel', - { - defaultMessage: 'French', - } - ), }, { + text: languageToText('de'), value: 'de', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.germanDropDownOptionLabel', - { - defaultMessage: 'German', - } - ), }, { + text: languageToText('it'), value: 'it', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.italianDropDownOptionLabel', - { - defaultMessage: 'Italian', - } - ), }, { + text: languageToText('ja'), value: 'ja', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.japaneseDropDownOptionLabel', - { - defaultMessage: 'Japanese', - } - ), }, { + text: languageToText('ko'), value: 'ko', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.koreanDropDownOptionLabel', - { - defaultMessage: 'Korean', - } - ), }, { + text: languageToText('pt'), value: 'pt', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.portugueseDropDownOptionLabel', - { - defaultMessage: 'Portuguese', - } - ), }, { + text: languageToText('pt-br'), value: 'pt-br', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.portugueseBrazilDropDownOptionLabel', - { - defaultMessage: 'Portuguese (Brazil)', - } - ), }, { + text: languageToText('ru'), value: 'ru', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.russianDropDownOptionLabel', - { - defaultMessage: 'Russian', - } - ), }, { + text: languageToText('es'), value: 'es', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.spanishDropDownOptionLabel', - { - defaultMessage: 'Spanish', - } - ), }, { + text: languageToText('th'), value: 'th', - text: i18n.translate( - 'xpack.enterpriseSearch.content.newIndex.supportedLanguages.thaiDropDownOptionLabel', - { - defaultMessage: 'Thai', - } - ), }, ]; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts index 4f85fd826cdb85..5a32fdba5e14b4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/constants.ts @@ -15,58 +15,58 @@ export const NATIVE_CONNECTORS: NativeConnector[] = [ { configuration: { host: { - value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.hostLabel', { - defaultMessage: 'MongoDB host', + defaultMessage: 'Host', } ), + value: '', }, user: { - value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.usernameLabel', { - defaultMessage: 'MongoDB username', + defaultMessage: 'Username', } ), + value: '', }, password: { - value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.passwordLabel', { - defaultMessage: 'MongoDB password', + defaultMessage: 'Password', } ), + value: '', }, database: { - value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.databaseLabel', { - defaultMessage: 'MongoDB database', + defaultMessage: 'Database', } ), + value: '', }, collection: { - value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.collectionLabel', { - defaultMessage: 'MongoDB collection', + defaultMessage: 'Collection', } ), + value: '', }, direct_connection: { - value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mongodb.configuration.directConnectionLabel', { - defaultMessage: 'Use direct connection (true/false)', + defaultMessage: 'Direct connection (true/false)', } ), + value: '', }, }, docsUrl: docLinks.connectorsMongoDB, @@ -80,49 +80,49 @@ export const NATIVE_CONNECTORS: NativeConnector[] = [ { configuration: { host: { - value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mysql.configuration.hostLabel', { - defaultMessage: 'MySQL host', + defaultMessage: 'Host', } ), + value: '', }, port: { - value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mysql.configuration.portLabel', { - defaultMessage: 'MySQL port', + defaultMessage: 'Port', } ), + value: '', }, user: { - value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mysql.configuration.usernameLabel', { - defaultMessage: 'MySQL username', + defaultMessage: 'Username', } ), + value: '', }, password: { value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mysql.configuration.passwordLabel', { - defaultMessage: 'MySQL password', + defaultMessage: 'Password', } ), }, database: { - value: '', label: i18n.translate( 'xpack.enterpriseSearch.content.nativeConnectors.mysql.configuration.databasesLabel', { - defaultMessage: 'List of MySQL databases', + defaultMessage: 'Databases', } ), + value: '', }, }, docsUrl: docLinks.connectorsMySQL, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector_total_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector_total_stats.tsx index 2b5ef4cb68df7e..fd70516a572d5d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector_total_stats.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector_total_stats.tsx @@ -21,6 +21,8 @@ import { i18n } from '@kbn/i18n'; import { isConnectorIndex } from '../../utils/indices'; +import { languageToText } from '../../utils/language_to_text'; + import { ConnectorOverviewPanels } from './connector/connector_overview_panels'; import { NATIVE_CONNECTORS } from './connector/constants'; import { NameAndDescriptionStats } from './name_and_description_stats'; @@ -71,11 +73,7 @@ export const ConnectorTotalStats: React.FC = () => { } ), isLoading: hideStats, - title: - indexData.connector.language ?? - i18n.translate('xpack.enterpriseSearch.content.searchIndex.totalStats.noneLabel', { - defaultMessage: 'None', - }), + title: languageToText(indexData.connector.language ?? ''), }, ]; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.test.tsx deleted file mode 100644 index a81ae20408aa09..00000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.test.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic'; -import '../../../../../__mocks__/shallow_useeffect.mock'; - -import React from 'react'; - -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButton, EuiFieldNumber, EuiForm, EuiSelect, EuiSwitch } from '@elastic/eui'; - -import { CrawlUnits } from '../../../../api/crawler/types'; - -import { AutomaticCrawlScheduler } from './automatic_crawl_scheduler'; - -const MOCK_ACTIONS = { - // AutomaticCrawlSchedulerLogic - setCrawlFrequency: jest.fn(), - setCrawlUnit: jest.fn(), - saveChanges: jest.fn(), - toggleCrawlAutomatically: jest.fn(), -}; - -const MOCK_VALUES = { - crawlAutomatically: false, - crawlFrequency: 7, - crawlUnit: CrawlUnits.days, - isSubmitting: false, -}; - -describe('AutomaticCrawlScheduler', () => { - let wrapper: ShallowWrapper; - - beforeEach(() => { - setMockActions(MOCK_ACTIONS); - setMockValues(MOCK_VALUES); - - wrapper = shallow(); - }); - - it('renders', () => { - expect(wrapper.find(EuiForm)).toHaveLength(1); - expect(wrapper.find(EuiFieldNumber)).toHaveLength(1); - expect(wrapper.find(EuiSelect)).toHaveLength(1); - }); - - it('saves changes on form submit', () => { - const preventDefault = jest.fn(); - wrapper.find(EuiForm).simulate('submit', { preventDefault }); - - expect(preventDefault).toHaveBeenCalled(); - expect(MOCK_ACTIONS.saveChanges).toHaveBeenCalled(); - }); - - it('contains a switch that toggles automatic crawling', () => { - wrapper.find(EuiSwitch).simulate('change'); - - expect(MOCK_ACTIONS.toggleCrawlAutomatically).toHaveBeenCalled(); - }); - - it('contains a number field that updates the crawl frequency', () => { - wrapper.find(EuiFieldNumber).simulate('change', { target: { value: '10' } }); - - expect(MOCK_ACTIONS.setCrawlFrequency).toHaveBeenCalledWith(10); - }); - - it('contains a select field that updates the crawl unit', () => { - wrapper.find(EuiSelect).simulate('change', { target: { value: CrawlUnits.weeks } }); - - expect(MOCK_ACTIONS.setCrawlUnit).toHaveBeenCalledWith(CrawlUnits.weeks); - }); - - it('contains a submit button', () => { - expect(wrapper.find(EuiButton).prop('type')).toEqual('submit'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx index 28c8b8ff10000a..960e432762722a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler.tsx @@ -10,175 +10,220 @@ import React from 'react'; import { useActions, useValues } from 'kea'; import { - EuiButton, + EuiCheckableCard, EuiFieldNumber, EuiFlexGroup, EuiFlexItem, - EuiForm, EuiFormRow, + EuiHorizontalRule, EuiLink, EuiSelect, EuiSpacer, + EuiSplitPanel, EuiSwitch, EuiText, - htmlIdGenerator, + EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - +import { CrawlerIndex } from '../../../../../../../common/types/indices'; import { HOURS_UNIT_LABEL, DAYS_UNIT_LABEL, WEEKS_UNIT_LABEL, MONTHS_UNIT_LABEL, - SAVE_BUTTON_LABEL, } from '../../../../../shared/constants'; -import { DataPanel } from '../../../../../shared/data_panel/data_panel'; - +import { EnterpriseSearchCronEditor } from '../../../../../shared/cron_editor/enterprise_search_cron_editor'; import { docLinks } from '../../../../../shared/doc_links/doc_links'; +import { UpdateConnectorSchedulingApiLogic } from '../../../../api/connector/update_connector_scheduling_api_logic'; import { CrawlUnits } from '../../../../api/crawler/types'; +import { isCrawlerIndex } from '../../../../utils/indices'; +import { IndexViewLogic } from '../../index_view_logic'; import { AutomaticCrawlSchedulerLogic } from './automatic_crawl_scheduler_logic'; export const AutomaticCrawlScheduler: React.FC = () => { - const { setCrawlFrequency, setCrawlUnit, saveChanges, toggleCrawlAutomatically } = useActions( - AutomaticCrawlSchedulerLogic - ); + const { index } = useValues(IndexViewLogic); + const { makeRequest } = useActions(UpdateConnectorSchedulingApiLogic); - const { crawlAutomatically, crawlFrequency, crawlUnit, isSubmitting } = useValues( + const scheduling = (index as CrawlerIndex)?.connector?.scheduling; + + const { setCrawlFrequency, setCrawlUnit, setUseConnectorSchedule, toggleCrawlAutomatically } = + useActions(AutomaticCrawlSchedulerLogic); + + const { crawlAutomatically, crawlFrequency, crawlUnit, useConnectorSchedule } = useValues( AutomaticCrawlSchedulerLogic ); - const formId = htmlIdGenerator('AutomaticCrawlScheduler')(); + if (!isCrawlerIndex(index)) { + return <>; + } return ( <> - - {i18n.translate('xpack.enterpriseSearch.automaticCrawlSchedule.title', { - defaultMessage: 'Automated Crawl Scheduling', - })} - - } - titleSize="s" - subtitle={ - - {i18n.translate( - 'xpack.enterpriseSearch.crawler.automaticCrawlSchedule.readMoreLink', - { - defaultMessage: 'Read more.', - } - )} - - ), - }} - /> - } - iconType="calendar" - > - { - event.preventDefault(); - saveChanges(); - }} - component="form" - id={formId} - > + +

+ {i18n.translate('xpack.enterpriseSearch.automaticCrawlSchedule.title', { + defaultMessage: 'Crawl frequency', + })} +

+
+ + + - {i18n.translate( - 'xpack.enterpriseSearch.crawler.automaticCrawlSchedule.crawlAutomaticallySwitchLabel', - { - defaultMessage: 'Crawl automatically', - } - )} - - } + label={i18n.translate( + 'xpack.enterpriseSearch.crawler.automaticCrawlSchedule.crawlAutomaticallySwitchLabel', + { + defaultMessage: 'Enable recurring crawls with the following schedule', + } + )} onChange={toggleCrawlAutomatically} compressed /> - - - - - {i18n.translate( - 'xpack.enterpriseSearch.crawler.automaticCrawlSchedule.crawlUnitsPrefix', - { - defaultMessage: 'Every', - } - )} - - - - setCrawlFrequency(parseInt(e.target.value, 10))} + + + + + + +
+ {i18n.translate( + 'xpack.enterpriseSearch.crawler.automaticCrawlSchedule.cronSchedulingTitle', + { + defaultMessage: 'Specific time scheduling', + } + )} +
+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.crawler.automaticCrawlSchedule.cronSchedulingDescription', + { + defaultMessage: 'Define the frequency and time for scheduled crawls', + } + )} + + + + } + checked={crawlAutomatically && useConnectorSchedule} + disabled={!crawlAutomatically} + onChange={() => setUseConnectorSchedule(true)} + > + + makeRequest({ + connectorId: index.connector.id, + scheduling: { ...newScheduling }, + }) + } /> -
- - setCrawlUnit(e.target.value as CrawlUnits)} - /> - - -
- + +
+ + + +
+ {i18n.translate( + 'xpack.enterpriseSearch.crawler.automaticCrawlSchedule.intervalSchedulingTitle', + { + defaultMessage: 'Interval scheduling', + } + )} +
+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.crawler.automaticCrawlSchedule.intervalSchedulingDescription', + { + defaultMessage: 'Define the frequency and time for scheduled crawls', + } + )} + + + + } + checked={crawlAutomatically && !useConnectorSchedule} + disabled={!crawlAutomatically} + onChange={() => setUseConnectorSchedule(false)} + > + + + + setCrawlFrequency(parseInt(e.target.value, 10))} + prepend={'Every'} + /> + + + setCrawlUnit(e.target.value as CrawlUnits)} + /> + + + +
+
+
{i18n.translate( @@ -188,21 +233,18 @@ export const AutomaticCrawlScheduler: React.FC = () => { 'The crawl schedule will perform a full crawl on every domain on this index.', } )} + + + {i18n.translate( + 'xpack.enterpriseSearch.crawler.automaticCrawlSchedule.readMoreLink', + { + defaultMessage: 'Learn more about scheduling', + } + )} + - - - - {SAVE_BUTTON_LABEL} - - - - + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.test.ts index 2327c1c394cee2..ac324eac83c373 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.test.ts @@ -21,7 +21,7 @@ import { AutomaticCrawlSchedulerLogic } from './automatic_crawl_scheduler_logic' describe('AutomaticCrawlSchedulerLogic', () => { const { mount } = new LogicMounter(AutomaticCrawlSchedulerLogic); const { http } = mockHttpValues; - const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers; + const { flashAPIErrors } = mockFlashMessageHelpers; beforeEach(() => { jest.clearAllMocks(); @@ -35,6 +35,7 @@ describe('AutomaticCrawlSchedulerLogic', () => { crawlFrequency: 24, crawlUnit: CrawlUnits.hours, isSubmitting: false, + useConnectorSchedule: false, }); }); @@ -102,6 +103,7 @@ describe('AutomaticCrawlSchedulerLogic', () => { AutomaticCrawlSchedulerLogic.actions.setCrawlSchedule({ frequency: 3, unit: CrawlUnits.hours, + useConnectorSchedule: true, }); expect(AutomaticCrawlSchedulerLogic.values).toMatchObject({ @@ -127,22 +129,8 @@ describe('AutomaticCrawlSchedulerLogic', () => { describe('listeners', () => { describe('deleteCrawlSchedule', () => { - it('resets the states of the crawl scheduler and popover, and shows a toast, on success', async () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'clearCrawlSchedule'); - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'onDoneSubmitting'); - http.delete.mockReturnValueOnce(Promise.resolve()); - - AutomaticCrawlSchedulerLogic.actions.deleteCrawlSchedule(); - await nextTick(); - - expect(AutomaticCrawlSchedulerLogic.actions.clearCrawlSchedule).toHaveBeenCalled(); - expect(flashSuccessToast).toHaveBeenCalledWith(expect.any(String)); - expect(AutomaticCrawlSchedulerLogic.actions.onDoneSubmitting).toHaveBeenCalled(); - }); - describe('error paths', () => { - it('resets the states of the crawl scheduler and popover on a 404 respose', async () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'clearCrawlSchedule'); + it('resets the states of the crawl scheduler on a 404 response', async () => { jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'onDoneSubmitting'); http.delete.mockReturnValueOnce( Promise.reject({ @@ -153,11 +141,10 @@ describe('AutomaticCrawlSchedulerLogic', () => { AutomaticCrawlSchedulerLogic.actions.deleteCrawlSchedule(); await nextTick(); - expect(AutomaticCrawlSchedulerLogic.actions.clearCrawlSchedule).toHaveBeenCalled(); expect(AutomaticCrawlSchedulerLogic.actions.onDoneSubmitting).toHaveBeenCalled(); }); - it('flashes an error on a non-404 respose', async () => { + it('flashes an error on a non-404 response', async () => { jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'onDoneSubmitting'); http.delete.mockReturnValueOnce( Promise.reject({ @@ -196,21 +183,7 @@ describe('AutomaticCrawlSchedulerLogic', () => { }); describe('error paths', () => { - it('resets the states of the crawl scheduler on a 404 respose', async () => { - jest.spyOn(AutomaticCrawlSchedulerLogic.actions, 'clearCrawlSchedule'); - http.get.mockReturnValueOnce( - Promise.reject({ - response: { status: 404 }, - }) - ); - - AutomaticCrawlSchedulerLogic.actions.fetchCrawlSchedule(); - await nextTick(); - - expect(AutomaticCrawlSchedulerLogic.actions.clearCrawlSchedule).toHaveBeenCalled(); - }); - - it('flashes an error on a non-404 respose', async () => { + it('flashes an error on a non-404 response', async () => { http.get.mockReturnValueOnce( Promise.reject({ response: { status: 500 }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.ts index 51452dbbd581af..04a11c6c182e6e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/automatic_crawl_scheduler/automatic_crawl_scheduler_logic.ts @@ -7,11 +7,10 @@ import { kea, MakeLogicType } from 'kea'; -import { i18n } from '@kbn/i18n'; - -import { flashAPIErrors, flashSuccessToast } from '../../../../../shared/flash_messages'; +import { flashAPIErrors } from '../../../../../shared/flash_messages'; import { HttpLogic } from '../../../../../shared/http'; -import { CrawlSchedule, CrawlUnits } from '../../../../api/crawler/types'; +import { CrawlSchedule, CrawlScheduleFromServer, CrawlUnits } from '../../../../api/crawler/types'; +import { crawlScheduleServerToClient } from '../../../../api/crawler/utils'; import { IndexNameLogic } from '../../index_name_logic'; export interface AutomaticCrawlSchedulerLogicValues { @@ -19,6 +18,7 @@ export interface AutomaticCrawlSchedulerLogicValues { crawlFrequency: CrawlSchedule['frequency']; crawlUnit: CrawlSchedule['unit']; isSubmitting: boolean; + useConnectorSchedule: CrawlSchedule['useConnectorSchedule']; } const DEFAULT_VALUES: Pick = { @@ -39,6 +39,9 @@ export interface AutomaticCrawlSchedulerLogicActions { }; setCrawlSchedule(crawlSchedule: CrawlSchedule): { crawlSchedule: CrawlSchedule }; setCrawlUnit(crawlUnit: CrawlSchedule['unit']): { crawlUnit: CrawlSchedule['unit'] }; + setUseConnectorSchedule(useConnectorSchedule: CrawlSchedule['useConnectorSchedule']): { + useConnectorSchedule: CrawlSchedule['useConnectorSchedule']; + }; submitCrawlSchedule(): void; toggleCrawlAutomatically(): void; } @@ -59,6 +62,7 @@ export const AutomaticCrawlSchedulerLogic = kea< submitCrawlSchedule: true, setCrawlFrequency: (crawlFrequency: string) => ({ crawlFrequency }), setCrawlUnit: (crawlUnit: CrawlUnits) => ({ crawlUnit }), + setUseConnectorSchedule: (useConnectorSchedule) => ({ useConnectorSchedule }), toggleCrawlAutomatically: true, }), reducers: () => ({ @@ -94,6 +98,14 @@ export const AutomaticCrawlSchedulerLogic = kea< submitCrawlSchedule: () => true, }, ], + useConnectorSchedule: [ + false, + { + setCrawlSchedule: (_, { crawlSchedule: { useConnectorSchedule = false } }) => + useConnectorSchedule, + setUseConnectorSchedule: (_, { useConnectorSchedule }) => useConnectorSchedule, + }, + ], }), listeners: ({ actions, values }) => ({ deleteCrawlSchedule: async () => { @@ -104,22 +116,10 @@ export const AutomaticCrawlSchedulerLogic = kea< await http.delete( `/internal/enterprise_search/indices/${indexName}/crawler/crawl_schedule` ); - actions.clearCrawlSchedule(); - flashSuccessToast( - i18n.translate( - 'xpack.enterpriseSearch.crawler.automaticCrawlScheduler.disableCrawlSchedule.successMessage', - { - defaultMessage: 'Automatic crawling has been disabled.', - } - ) - ); } catch (e) { // A 404 is expected and means the user has no crawl schedule to delete - if (e.response?.status === 404) { - actions.clearCrawlSchedule(); - } else { + if (e.response?.status !== 404) { flashAPIErrors(e); - // Keep the popover open } } finally { actions.onDoneSubmitting(); @@ -130,16 +130,14 @@ export const AutomaticCrawlSchedulerLogic = kea< const { indexName } = IndexNameLogic.values; try { - const crawlSchedule: CrawlSchedule = await http.get( + const crawlSchedule: CrawlScheduleFromServer = await http.get( `/internal/enterprise_search/indices/${indexName}/crawler/crawl_schedule` ); - actions.setCrawlSchedule(crawlSchedule); + actions.setCrawlSchedule(crawlScheduleServerToClient(crawlSchedule)); } catch (e) { // A 404 is expected and means the user does not have crawl schedule // for this index. We continue to use the defaults. - if (e.response?.status === 404) { - actions.clearCrawlSchedule(); - } else { + if (e.response?.status !== 404) { flashAPIErrors(e); } } @@ -151,29 +149,30 @@ export const AutomaticCrawlSchedulerLogic = kea< actions.deleteCrawlSchedule(); } }, + setCrawlUnit: actions.saveChanges, + setCrawlFrequency: actions.saveChanges, + setUseConnectorSchedule: actions.saveChanges, + toggleCrawlAutomatically: actions.saveChanges, submitCrawlSchedule: async () => { const { http } = HttpLogic.values; const { indexName } = IndexNameLogic.values; + if (!values.crawlUnit || !values.crawlFrequency) { + return; + } + try { - const crawlSchedule: CrawlSchedule = await http.put( + const crawlSchedule: CrawlScheduleFromServer = await http.put( `/internal/enterprise_search/indices/${indexName}/crawler/crawl_schedule`, { body: JSON.stringify({ unit: values.crawlUnit, frequency: values.crawlFrequency, + use_connector_schedule: values.useConnectorSchedule, }), } ); - actions.setCrawlSchedule(crawlSchedule); - flashSuccessToast( - i18n.translate( - 'xpack.enterpriseSearch.crawler.automaticCrawlScheduler.submitCrawlSchedule.successMessage', - { - defaultMessage: 'Your automatic crawling schedule has been updated.', - } - ) - ); + actions.setCrawlSchedule(crawlScheduleServerToClient(crawlSchedule)); } catch (e) { flashAPIErrors(e); } finally { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx index f83dfd3fea11bf..c0cb7dd1375f1f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/inference_pipeline_card.tsx @@ -152,7 +152,10 @@ export const InferencePipelineCard: React.FC = (pipeline) =>
)} - + {pipeline.modelState === TrainedModelState.NotDeployed && ( 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 2805b8389913da..a5c7ff4b67e146 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 @@ -6,6 +6,7 @@ */ import { LogicMounter } from '../../../../../__mocks__/kea_logic'; +import { nerModel } from '../../../../__mocks__/ml_models.mock'; import { HttpResponse } from '@kbn/core/public'; import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; @@ -13,6 +14,7 @@ import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_ import { ErrorResponse, HttpError, Status } from '../../../../../../../common/types/api'; import { TrainedModelState } from '../../../../../../../common/types/pipelines'; +import { GetDocumentsApiLogic } from '../../../../api/documents/get_document_logic'; import { MappingsApiLogic } from '../../../../api/mappings/mappings_logic'; import { MLModelsApiLogic } from '../../../../api/ml_models/ml_models_logic'; import { AttachMlInferencePipelineApiLogic } from '../../../../api/pipelines/attach_ml_inference_pipeline'; @@ -35,22 +37,8 @@ const DEFAULT_VALUES: MLInferenceProcessorsValues = { ...EMPTY_PIPELINE_CONFIGURATION, }, indexName: '', - simulateBody: ` -[ - { - "_index": "index", - "_id": "id", - "_source": { - "foo": "bar" - } - }, - { - "_index": "index", - "_id": "id", - "_source": { - "foo": "baz" - } - } + simulateBody: `[ + ]`, step: AddInferencePipelineSteps.Configuration, }, @@ -61,7 +49,12 @@ const DEFAULT_VALUES: MLInferenceProcessorsValues = { pipelineName: 'Field is required.', sourceField: 'Field is required.', }, + getDocumentApiErrorMessage: undefined, + getDocumentApiStatus: Status.IDLE, + getDocumentData: undefined, + getDocumentsErr: '', index: null, + isGetDocumentsLoading: false, isLoading: true, isPipelineDataValid: false, mappingData: undefined, @@ -69,8 +62,9 @@ const DEFAULT_VALUES: MLInferenceProcessorsValues = { mlInferencePipeline: undefined, mlInferencePipelineProcessors: undefined, mlInferencePipelinesData: undefined, - mlModelsData: undefined, + mlModelsData: null, mlModelsStatus: 0, + showGetDocumentErrors: false, simulateExistingPipelineData: undefined, simulateExistingPipelineStatus: 0, simulatePipelineData: undefined, @@ -103,6 +97,7 @@ describe('MlInferenceLogic', () => { const { mount: mountFetchMlInferencePipelinesApiLogic } = new LogicMounter( FetchMlInferencePipelinesApiLogic ); + const { mount: mountGetDocumentsApiLogic } = new LogicMounter(GetDocumentsApiLogic); beforeEach(() => { jest.clearAllMocks(); @@ -114,6 +109,7 @@ describe('MlInferenceLogic', () => { mountSimulateMlInterfacePipelineApiLogic(); mountCreateMlInferencePipelineApiLogic(); mountAttachMlInferencePipelineApiLogic(); + mountGetDocumentsApiLogic(); mount(); }); @@ -197,13 +193,35 @@ describe('MlInferenceLogic', () => { expect(MLInferenceLogic.values.createErrors).not.toHaveLength(0); MLInferenceLogic.actions.makeCreatePipelineRequest({ indexName: 'test', - pipelineName: 'unit-test', modelId: 'test-model', + pipelineName: 'unit-test', sourceField: 'body', }); expect(MLInferenceLogic.values.createErrors).toHaveLength(0); }); }); + describe('getDocumentApiSuccess', () => { + it('sets simulateBody text to the returned document', () => { + GetDocumentsApiLogic.actions.apiSuccess({ + _id: 'test-index-123', + _index: 'test-index', + found: true, + }); + expect(MLInferenceLogic.values.addInferencePipelineModal.simulateBody).toEqual( + JSON.stringify( + [ + { + _id: 'test-index-123', + _index: 'test-index', + found: true, + }, + ], + undefined, + 2 + ) + ); + }); + }); }); describe('selectors', () => { @@ -331,9 +349,9 @@ describe('MlInferenceLogic', () => { { destinationField: 'test-field', disabled: false, - pipelineName: 'unit-test', - modelType: '', modelId: 'test-model', + modelType: '', + pipelineName: 'unit-test', sourceField: 'body', }, ]); @@ -361,9 +379,9 @@ describe('MlInferenceLogic', () => { destinationField: 'test-field', disabled: true, disabledReason: expect.any(String), - pipelineName: 'unit-test', - modelType: '', modelId: 'test-model', + modelType: '', + pipelineName: 'unit-test', sourceField: 'body_content', }, ]); @@ -449,28 +467,10 @@ describe('MlInferenceLogic', () => { expect(MLInferenceLogic.values.mlInferencePipeline).toBeUndefined(); }); it('generates inference pipeline', () => { - MLModelsApiLogic.actions.apiSuccess([ - { - inference_config: { - text_classification: { - classification_labels: ['one', 'two'], - tokenization: { - bert: {}, - }, - }, - }, - input: { - field_names: ['text_field'], - }, - model_id: 'test-model', - model_type: 'pytorch', - tags: [], - version: '1.0.0', - }, - ]); + MLModelsApiLogic.actions.apiSuccess([nerModel]); MLInferenceLogic.actions.setInferencePipelineConfiguration({ destinationField: '', - modelID: 'test-model', + modelID: nerModel.model_id, pipelineName: 'unit-test', sourceField: 'body', }); @@ -507,6 +507,46 @@ describe('MlInferenceLogic', () => { expect(MLInferenceLogic.values.mlInferencePipeline).toEqual(existingPipeline); }); }); + describe('getDocumentsErr', () => { + it('returns empty string when no error is present', () => { + GetDocumentsApiLogic.actions.apiSuccess({ + _id: 'test-123', + _index: 'test', + found: true, + }); + expect(MLInferenceLogic.values.getDocumentsErr).toEqual(''); + }); + it('returns extracted error message from the http response', () => { + GetDocumentsApiLogic.actions.apiError({ + body: { + error: 'document-not-found', + message: 'not-found', + statusCode: 404, + }, + } as HttpError); + expect(MLInferenceLogic.values.getDocumentsErr).toEqual('not-found'); + }); + }); + describe('showGetDocumentErrors', () => { + it('returns false when no error is present', () => { + GetDocumentsApiLogic.actions.apiSuccess({ + _id: 'test-123', + _index: 'test', + found: true, + }); + expect(MLInferenceLogic.values.showGetDocumentErrors).toEqual(false); + }); + it('returns true when an error message is present', () => { + GetDocumentsApiLogic.actions.apiError({ + body: { + error: 'document-not-found', + message: 'not-found', + statusCode: 404, + }, + } as HttpError); + expect(MLInferenceLogic.values.showGetDocumentErrors).toEqual(true); + }); + }); }); describe('listeners', () => { @@ -568,26 +608,13 @@ describe('MlInferenceLogic', () => { ...DEFAULT_VALUES.addInferencePipelineModal, configuration: { destinationField: '', - modelID: 'mock-model-id', + modelID: nerModel.model_id, pipelineName: 'mock-pipeline-name', sourceField: 'mock_text_field', }, indexName: 'my-index-123', }; - const mlModelsData: TrainedModelConfigResponse[] = [ - { - inference_config: { - text_classification: {}, - }, - input: { - field_names: ['text_field'], - }, - model_id: 'mock-model-id', - model_type: 'pytorch', - tags: ['test_tag'], - version: '1', - }, - ]; + const mlModelsData: TrainedModelConfigResponse[] = [nerModel]; it('does nothing if mlInferencePipeline is undefined', () => { mount({ ...DEFAULT_VALUES, 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 4b92c43b2b3046..c897ee8007738e 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 @@ -10,19 +10,23 @@ import { kea, MakeLogicType } from 'kea'; import { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; import { IngestSimulateResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; - import { formatPipelineName, generateMlInferencePipelineBody, getMlModelTypesForModelConfig, parseMlInferenceParametersFromPipeline, } from '../../../../../../../common/ml_inference_pipeline'; -import { Status } from '../../../../../../../common/types/api'; +import { Status, HttpError } from '../../../../../../../common/types/api'; import { MlInferencePipeline } from '../../../../../../../common/types/pipelines'; import { Actions } from '../../../../../shared/api_logic/create_api_logic'; import { getErrorsFromHttpResponse } from '../../../../../shared/flash_messages/handle_api_errors'; + +import { + GetDocumentsApiLogic, + GetDocumentsArgs, + GetDocumentsResponse, +} from '../../../../api/documents/get_document_logic'; import { CachedFetchIndexApiLogic, CachedFetchIndexApiLogicValues, @@ -33,10 +37,10 @@ import { MappingsApiLogic, } from '../../../../api/mappings/mappings_logic'; import { - GetMlModelsArgs, - GetMlModelsResponse, - MLModelsApiLogic, -} from '../../../../api/ml_models/ml_models_logic'; + TrainedModel, + TrainedModelsApiLogicActions, + TrainedModelsApiLogic, +} from '../../../../api/ml_models/ml_trained_models_logic'; import { AttachMlInferencePipelineApiLogic, AttachMlInferencePipelineApiLogicArgs, @@ -127,6 +131,8 @@ interface MLInferenceProcessorsActions { CreateMlInferencePipelineResponse >['apiSuccess']; createPipeline: () => void; + getDocumentApiError: Actions['apiError']; + getDocumentApiSuccess: Actions['apiSuccess']; makeAttachPipelineRequest: Actions< AttachMlInferencePipelineApiLogicArgs, AttachMlInferencePipelineResponse @@ -135,7 +141,8 @@ interface MLInferenceProcessorsActions { CreateMlInferencePipelineApiLogicArgs, CreateMlInferencePipelineResponse >['makeRequest']; - makeMLModelsRequest: Actions['makeRequest']; + makeGetDocumentRequest: Actions['makeRequest']; + makeMLModelsRequest: TrainedModelsApiLogicActions['makeRequest']; makeMappingRequest: Actions['makeRequest']; makeMlInferencePipelinesRequest: Actions< FetchMlInferencePipelinesArgs, @@ -150,7 +157,7 @@ interface MLInferenceProcessorsActions { SimulateMlInterfacePipelineResponse >['makeRequest']; mappingsApiError: Actions['apiError']; - mlModelsApiError: Actions['apiError']; + mlModelsApiError: TrainedModelsApiLogicActions['apiError']; selectExistingPipeline: (pipelineName: string) => { pipelineName: string; }; @@ -204,7 +211,12 @@ export interface MLInferenceProcessorsValues { createErrors: string[]; existingInferencePipelines: MLInferencePipelineOption[]; formErrors: AddInferencePipelineFormErrors; + getDocumentApiErrorMessage: HttpError | undefined; + getDocumentApiStatus: Status; + getDocumentData: typeof GetDocumentsApiLogic.values.data; + getDocumentsErr: string; index: CachedFetchIndexApiLogicValues['indexData']; + isGetDocumentsLoading: boolean; isLoading: boolean; isPipelineDataValid: boolean; mappingData: typeof MappingsApiLogic.values.data; @@ -212,8 +224,9 @@ export interface MLInferenceProcessorsValues { mlInferencePipeline: MlInferencePipeline | undefined; mlInferencePipelineProcessors: FetchMlInferencePipelineProcessorsResponse | undefined; mlInferencePipelinesData: FetchMlInferencePipelinesResponse | undefined; - mlModelsData: TrainedModelConfigResponse[] | undefined; + mlModelsData: TrainedModel[] | null; mlModelsStatus: Status; + showGetDocumentErrors: boolean; simulateExistingPipelineData: typeof SimulateExistingMlInterfacePipelineApiLogic.values.data; simulateExistingPipelineStatus: Status; simulatePipelineData: typeof SimulateMlInterfacePipelineApiLogic.values.data; @@ -221,7 +234,7 @@ export interface MLInferenceProcessorsValues { simulatePipelineResult: IngestSimulateResponse | undefined; simulatePipelineStatus: Status; sourceFields: string[] | undefined; - supportedMLModels: TrainedModelConfigResponse[]; + supportedMLModels: TrainedModel[]; } export const MLInferenceLogic = kea< @@ -250,7 +263,7 @@ export const MLInferenceLogic = kea< ['makeRequest as makeMlInferencePipelinesRequest'], MappingsApiLogic, ['makeRequest as makeMappingRequest', 'apiError as mappingsApiError'], - MLModelsApiLogic, + TrainedModelsApiLogic, ['makeRequest as makeMLModelsRequest', 'apiError as mlModelsApiError'], SimulateExistingMlInterfacePipelineApiLogic, [ @@ -278,6 +291,12 @@ export const MLInferenceLogic = kea< 'apiSuccess as attachApiSuccess', 'makeRequest as makeAttachPipelineRequest', ], + GetDocumentsApiLogic, + [ + 'apiError as getDocumentApiError', + 'apiSuccess as getDocumentApiSuccess', + 'makeRequest as makeGetDocumentRequest', + ], ], values: [ CachedFetchIndexApiLogic, @@ -286,7 +305,7 @@ export const MLInferenceLogic = kea< ['data as mlInferencePipelinesData'], MappingsApiLogic, ['data as mappingData', 'status as mappingStatus'], - MLModelsApiLogic, + TrainedModelsApiLogic, ['data as mlModelsData', 'status as mlModelsStatus'], SimulateExistingMlInterfacePipelineApiLogic, ['data as simulateExistingPipelineData', 'status as simulateExistingPipelineStatus'], @@ -294,6 +313,12 @@ export const MLInferenceLogic = kea< ['data as simulatePipelineData', 'status as simulatePipelineStatus'], FetchMlInferencePipelineProcessorsApiLogic, ['data as mlInferencePipelineProcessors'], + GetDocumentsApiLogic, + [ + 'data as getDocumentData', + 'status as getDocumentApiStatus', + 'error as getDocumentApiErrorMessage', + ], ], }, events: {}, @@ -375,26 +400,16 @@ export const MLInferenceLogic = kea< ...EMPTY_PIPELINE_CONFIGURATION, }, indexName: '', - simulateBody: ` -[ - { - "_index": "index", - "_id": "id", - "_source": { - "foo": "bar" - } - }, - { - "_index": "index", - "_id": "id", - "_source": { - "foo": "baz" - } - } + simulateBody: `[ + ]`, step: AddInferencePipelineSteps.Configuration, }, { + getDocumentApiSuccess: (modal, doc) => ({ + ...modal, + simulateBody: JSON.stringify([doc], undefined, 2), + }), setAddInferencePipelineStep: (modal, { step }) => ({ ...modal, step }), setIndexName: (modal, { indexName }) => ({ ...modal, indexName }), setInferencePipelineConfiguration: (modal, { configuration }) => ({ @@ -420,8 +435,8 @@ export const MLInferenceLogic = kea< [], { setSimulatePipelineErrors: (_, { errors }) => errors, - simulatePipelineApiError: (_, error) => getErrorsFromHttpResponse(error), simulateExistingPipelineApiError: (_, error) => getErrorsFromHttpResponse(error), + simulatePipelineApiError: (_, error) => getErrorsFromHttpResponse(error), }, ], }, @@ -431,6 +446,19 @@ export const MLInferenceLogic = kea< (modal: AddInferencePipelineModal) => validateInferencePipelineConfiguration(modal.configuration), ], + getDocumentsErr: [ + () => [selectors.getDocumentApiErrorMessage], + (err: MLInferenceProcessorsValues['getDocumentApiErrorMessage']) => { + if (!err) return ''; + return getErrorsFromHttpResponse(err)[0]; + }, + ], + isGetDocumentsLoading: [ + () => [selectors.getDocumentApiStatus], + (status) => { + return status === Status.LOADING; + }, + ], isLoading: [ () => [selectors.mlModelsStatus, selectors.mappingStatus], (mlModelsStatus, mappingStatus) => @@ -441,6 +469,12 @@ export const MLInferenceLogic = kea< () => [selectors.formErrors], (errors: AddInferencePipelineFormErrors) => Object.keys(errors).length === 0, ], + showGetDocumentErrors: [ + () => [selectors.getDocumentApiStatus], + (status: MLInferenceProcessorsValues['getDocumentApiStatus']) => { + return status === Status.ERROR; + }, + ], mlInferencePipeline: [ () => [ selectors.isPipelineDataValid, @@ -521,7 +555,7 @@ export const MLInferenceLogic = kea< ], supportedMLModels: [ () => [selectors.mlModelsData], - (mlModelsData: TrainedModelConfigResponse[] | undefined) => { + (mlModelsData: MLInferenceProcessorsValues['mlModelsData']) => { return mlModelsData?.filter(isSupportedMLModel) ?? []; }, ], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.tsx index 4529e85d720f83..955687e39f762b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_select_option.tsx @@ -8,13 +8,19 @@ import React from 'react'; import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiTextColor, EuiTitle } from '@elastic/eui'; -import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; -import { getMlModelTypesForModelConfig } from '../../../../../../../common/ml_inference_pipeline'; +import { + getMlModelTypesForModelConfig, + parseModelStateFromStats, + parseModelStateReasonFromStats, +} from '../../../../../../../common/ml_inference_pipeline'; +import { TrainedModel } from '../../../../api/ml_models/ml_trained_models_logic'; import { getMLType, getModelDisplayTitle } from '../../../shared/ml_inference/utils'; +import { TrainedModelHealth } from '../ml_model_health'; + export interface MlModelSelectOptionProps { - model: TrainedModelConfigResponse; + model: TrainedModel; } export const MlModelSelectOption: React.FC = ({ model }) => { const type = getMLType(getMlModelTypesForModelConfig(model)); @@ -34,9 +40,19 @@ export const MlModelSelectOption: React.FC = ({ model )} - - {type} - + + + + + + + {type} + + +
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/test_pipeline.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/test_pipeline.tsx index bd5b561426cfa6..a1f7b316b9d481 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/test_pipeline.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/test_pipeline.tsx @@ -5,23 +5,26 @@ * 2.0. */ -import React from 'react'; +import React, { useRef } from 'react'; import { useValues, useActions } from 'kea'; import { - EuiCodeBlock, - EuiResizableContainer, EuiButton, - EuiText, + EuiCode, + EuiCodeBlock, + EuiFieldText, EuiFlexGroup, EuiFlexItem, - useIsWithinMaxBreakpoint, + EuiFormRow, + EuiResizableContainer, EuiSpacer, + EuiText, + useIsWithinMaxBreakpoint, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - +import { FormattedMessage } from '@kbn/i18n-react'; import { CodeEditor } from '@kbn/kibana-react-plugin/public'; import { MLInferenceLogic } from './ml_inference_logic'; @@ -30,25 +33,81 @@ import './add_ml_inference_pipeline_modal.scss'; export const TestPipeline: React.FC = () => { const { - addInferencePipelineModal: { simulateBody }, + addInferencePipelineModal: { + configuration: { sourceField }, + indexName, + simulateBody, + }, + getDocumentsErr, + isGetDocumentsLoading, + showGetDocumentErrors, simulatePipelineResult, simulatePipelineErrors, } = useValues(MLInferenceLogic); - const { simulatePipeline, setPipelineSimulateBody } = useActions(MLInferenceLogic); + const { simulatePipeline, setPipelineSimulateBody, makeGetDocumentRequest } = + useActions(MLInferenceLogic); const isSmallerViewport = useIsWithinMaxBreakpoint('s'); + const inputRef = useRef(); return ( - -

- {i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.test.title', - { defaultMessage: 'Review pipeline results (optional)' } - )} -

-
+ + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.test.title', + { defaultMessage: 'Review pipeline results (optional)' } + )} +

+
+ + +
+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.test.subtitle', + { defaultMessage: 'Documents' } + )} +
+
+
+ + + { + inputRef.current = ref; + }} + isInvalid={showGetDocumentErrors} + isLoading={isGetDocumentsLoading} + onKeyDown={(e) => { + if (e.key === 'Enter' && inputRef.current?.value.trim().length !== 0) { + makeGetDocumentRequest({ + documentId: inputRef.current?.value.trim() ?? '', + indexName, + }); + } + }} + /> + + +
{ - +

{i18n.translate( 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.test.description', @@ -98,6 +157,16 @@ export const TestPipeline: React.FC = () => { 'You can simulate your pipeline results by passing an array of documents.', } )} +
+ {`[{"_index":"index","_id":"id","_source":{"${sourceField}":"bar"}}]`} + ), + }} + />

diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx index 57f82b277467f0..b2362b9e300e93 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.test.tsx @@ -35,7 +35,10 @@ describe('TrainedModelHealth', () => { ...commonModelData, modelState: TrainedModelState.Started, }; - const wrapper = shallow(); + const { modelState, modelStateReason } = pipeline; + const wrapper = shallow( + + ); const health = wrapper.find(EuiHealth); expect(health.prop('children')).toEqual('Started'); expect(health.prop('color')).toEqual('success'); @@ -44,7 +47,10 @@ describe('TrainedModelHealth', () => { const pipeline: InferencePipeline = { ...commonModelData, }; - const wrapper = shallow(); + const { modelState, modelStateReason } = pipeline; + const wrapper = shallow( + + ); const health = wrapper.find(EuiHealth); expect(health.prop('children')).toEqual('Not deployed'); expect(health.prop('color')).toEqual('danger'); @@ -54,7 +60,10 @@ describe('TrainedModelHealth', () => { ...commonModelData, modelState: TrainedModelState.Stopping, }; - const wrapper = shallow(); + const { modelState, modelStateReason } = pipeline; + const wrapper = shallow( + + ); const health = wrapper.find(EuiHealth); expect(health.prop('children')).toEqual('Stopping'); expect(health.prop('color')).toEqual('warning'); @@ -64,7 +73,10 @@ describe('TrainedModelHealth', () => { ...commonModelData, modelState: TrainedModelState.Starting, }; - const wrapper = shallow(); + const { modelState, modelStateReason } = pipeline; + const wrapper = shallow( + + ); const health = wrapper.find(EuiHealth); expect(health.prop('children')).toEqual('Starting'); expect(health.prop('color')).toEqual('warning'); @@ -75,7 +87,10 @@ describe('TrainedModelHealth', () => { modelState: TrainedModelState.Failed, modelStateReason: 'Model start boom.', }; - const wrapper = shallow(); + const { modelState, modelStateReason } = pipeline; + const wrapper = shallow( + + ); const health = wrapper.find(EuiHealth); expect(health.prop('children')).toEqual('Deployment failed'); expect(health.prop('color')).toEqual('danger'); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx index 0d47c7018d4fee..7ea901f74b1a56 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_model_health.tsx @@ -12,7 +12,7 @@ import { EuiHealth, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; +import { TrainedModelState } from '../../../../../../common/types/pipelines'; const modelStartedText = i18n.translate( 'xpack.enterpriseSearch.inferencePipelineCard.modelState.started', @@ -72,7 +72,12 @@ const modelNotDeployedTooltip = i18n.translate( } ); -export const TrainedModelHealth: React.FC = ({ +export interface TrainedModelHealthProps { + modelState: TrainedModelState; + modelStateReason?: string; +} + +export const TrainedModelHealth: React.FC = ({ modelState, modelStateReason, }) => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_job_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_job_flyout.tsx index 521db7971c4602..f1385c5223e52b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_job_flyout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_job_flyout.tsx @@ -88,13 +88,13 @@ export const SyncJobFlyout: React.FC = ({ onClose, syncJob }
- {syncJob.pipeline && ( + {syncJob.connector?.pipeline && ( - + )}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx index 193df13c3ad98d..822add8f8bde3d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx @@ -61,9 +61,17 @@ export const SyncJobs: React.FC = () => { truncateText: true, }, { - field: 'docsCount', - name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.docsCount.columnTitle', { - defaultMessage: 'Docs count', + field: 'indexed_document_count', + name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.addedDocs.columnTitle', { + defaultMessage: 'Docs added', + }), + sortable: true, + truncateText: true, + }, + { + field: 'deleted_document_count', + name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.deletedDocs.columnTitle', { + defaultMessage: 'Docs deleted', }), sortable: true, truncateText: true, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.test.ts index 49f8b76476c515..544d48110941ac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.test.ts @@ -63,18 +63,23 @@ describe('SyncJobsViewLogic', () => { cancelation_requested_at: null, canceled_at: null, completed_at: '2022-09-05T15:59:39.816+00:00', - connector_id: 'we2284IBjobuR2-lAuXh', + connector: { + configuration: {}, + filtering: null, + id: 'we2284IBjobuR2-lAuXh', + index_name: 'indexName', + language: 'something', + pipeline: null, + service_type: '', + }, created_at: '2022-09-05T14:59:39.816+00:00', deleted_document_count: 20, error: null, - filtering: null, id: 'id', - index_name: 'indexName', indexed_document_count: 50, indexed_document_volume: 40, last_seen: '2022-09-05T15:59:39.816+00:00', metadata: {}, - pipeline: null, started_at: '2022-09-05T14:59:39.816+00:00', status: SyncStatus.COMPLETED, trigger_method: TriggerMethod.ON_DEMAND, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_stats.tsx new file mode 100644 index 00000000000000..35610ee8035014 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_stats.tsx @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { Status } from '../../../../../common/types/api'; + +import { FetchSyncJobsStatsApiLogic } from '../../api/stats/fetch_sync_jobs_stats_api_logic'; + +export const IndicesStats: React.FC = () => { + const { makeRequest } = useActions(FetchSyncJobsStatsApiLogic); + const { data, status } = useValues(FetchSyncJobsStatsApiLogic); + const isLoading = status === Status.LOADING; + + useEffect(() => { + makeRequest({}); + }, []); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx index f2efea5ef1e51a..7d328dec9887fb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx @@ -36,6 +36,7 @@ import { EnterpriseSearchContentPageTemplate } from '../layout/page_template'; import { DeleteIndexModal } from './delete_index_modal'; import { IndicesLogic } from './indices_logic'; +import { IndicesStats } from './indices_stats'; import { IndicesTable } from './indices_table'; import './search_indices.scss'; @@ -148,6 +149,9 @@ export const SearchIndices: React.FC = () => { )} + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.test.ts index cc4a158f41ef6e..204435e33f52d6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.test.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { nerModel, textClassificationModel } from '../../../__mocks__/ml_models.mock'; import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; @@ -19,33 +20,23 @@ describe('ml inference utils', () => { describe('isSupportedMLModel', () => { const makeFakeModel = ( config: Partial - ): TrainedModelConfigResponse => ({ - inference_config: {}, - input: { - field_names: [], - }, - model_id: 'a-model-001', - model_type: 'pytorch', - tags: [], - version: '1', - ...config, - }); + ): TrainedModelConfigResponse => { + const { inference_config: _throwAway, ...base } = nerModel; + return { + inference_config: {}, + ...base, + ...config, + }; + }; it('returns true for expected models', () => { const models: TrainedModelConfigResponse[] = [ - makeFakeModel({ - inference_config: { - ner: {}, - }, - }), - makeFakeModel({ - inference_config: { - text_classification: {}, - }, - }), + nerModel, + textClassificationModel, makeFakeModel({ inference_config: { text_embedding: {}, }, + model_id: 'mock-text_embedding', }), makeFakeModel({ inference_config: { @@ -53,16 +44,19 @@ describe('ml inference utils', () => { classification_labels: [], }, }, + model_id: 'mock-zero_shot_classification', }), makeFakeModel({ inference_config: { question_answering: {}, }, + model_id: 'mock-question_answering', }), makeFakeModel({ inference_config: { fill_mask: {}, }, + model_id: 'mock-fill_mask', }), ]; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/language_to_text.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/language_to_text.ts new file mode 100644 index 00000000000000..34b7f9fdcd7ff8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/utils/language_to_text.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 UNIVERSAL_LANGUAGE_VALUE = ''; + +export const languageToTextMap: Record = { + [UNIVERSAL_LANGUAGE_VALUE]: i18n.translate( + 'xpack.enterpriseSearch.content.supportedLanguages.universalLabel', + { + defaultMessage: 'Universal', + } + ), + da: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.danishLabel', { + defaultMessage: 'Danish', + }), + de: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.germanLabel', { + defaultMessage: 'German', + }), + en: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.englishLabel', { + defaultMessage: 'English', + }), + es: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.spanishLabel', { + defaultMessage: 'Spanish', + }), + + fr: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.frenchLabel', { + defaultMessage: 'French', + }), + + it: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.italianLabel', { + defaultMessage: 'Italian', + }), + ja: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.japaneseLabel', { + defaultMessage: 'Japanese', + }), + ko: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.koreanLabel', { + defaultMessage: 'Korean', + }), + + nl: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.dutchLabel', { + defaultMessage: 'Dutch', + }), + pt: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.portugueseLabel', { + defaultMessage: 'Portuguese', + }), + 'pt-br': i18n.translate( + 'xpack.enterpriseSearch.content.supportedLanguages.portugueseBrazilLabel', + { + defaultMessage: 'Portuguese (Brazil)', + } + ), + ru: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.russianLabel', { + defaultMessage: 'Russian', + }), + th: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.thaiLabel', { + defaultMessage: 'Thai', + }), + zh: i18n.translate('xpack.enterpriseSearch.content.supportedLanguages.chineseLabel', { + defaultMessage: 'Chinese', + }), +}; + +export function languageToText(input: string): string { + return languageToTextMap[input] ?? input; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/constants.ts new file mode 100644 index 00000000000000..ad0c2bb45cc393 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/constants.ts @@ -0,0 +1,155 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { padStart } from 'lodash'; + +import { EuiSelectOption } from '@elastic/eui'; + +import { DayOrdinal, MonthOrdinal, getOrdinalValue, getDayName, getMonthName } from './services'; +import { Frequency, Field, FieldToValueMap } from './types'; + +type FieldFlags = { + [key in Field]?: boolean; +}; + +function makeSequence(min: number, max: number): number[] { + const values = []; + for (let i = min; i <= max; i++) { + values.push(i); + } + return values; +} + +export const MINUTE_OPTIONS = makeSequence(0, 59).map((value) => ({ + value: value.toString(), + text: padStart(value.toString(), 2, '0'), +})); + +export const HOUR_OPTIONS = makeSequence(0, 23).map((value) => ({ + value: value.toString(), + text: padStart(value.toString(), 2, '0'), +})); + +export const DAY_OPTIONS = makeSequence(1, 7).map((value) => ({ + value: value.toString(), + text: getDayName((value - 1) as DayOrdinal), +})); + +export const DATE_OPTIONS = makeSequence(1, 31).map((value) => ({ + value: value.toString(), + text: getOrdinalValue(value), +})); + +export const MONTH_OPTIONS = makeSequence(1, 12).map((value) => ({ + value: value.toString(), + text: getMonthName((value - 1) as MonthOrdinal), +})); + +export const UNITS: EuiSelectOption[] = [ + { + value: 'MINUTE', + text: 'minute', + }, + { + value: 'HOUR', + text: 'hour', + }, + { + value: 'DAY', + text: 'day', + }, + { + value: 'WEEK', + text: 'week', + }, + { + value: 'MONTH', + text: 'month', + }, + { + value: 'YEAR', + text: 'year', + }, +]; + +export const frequencyToFieldsMap: Record = { + MINUTE: {}, + HOUR: { + minute: true, + }, + DAY: { + hour: true, + minute: true, + }, + WEEK: { + day: true, + hour: true, + minute: true, + }, + MONTH: { + date: true, + hour: true, + minute: true, + }, + YEAR: { + month: true, + date: true, + hour: true, + minute: true, + }, +}; + +export const frequencyToBaselineFieldsMap: Record = { + MINUTE: { + second: '0', + minute: '*', + hour: '*', + date: '*', + month: '*', + day: '?', + }, + HOUR: { + second: '0', + minute: '0', + hour: '*', + date: '*', + month: '*', + day: '?', + }, + DAY: { + second: '0', + minute: '0', + hour: '0', + date: '*', + month: '*', + day: '?', + }, + WEEK: { + second: '0', + minute: '0', + hour: '0', + date: '?', + month: '*', + day: '7', + }, + MONTH: { + second: '0', + minute: '0', + hour: '0', + date: '1', + month: '*', + day: '?', + }, + YEAR: { + second: '0', + minute: '0', + hour: '0', + date: '1', + month: '1', + day: '?', + }, +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_daily.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_daily.tsx new file mode 100644 index 00000000000000..5fb736fa263954 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_daily.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Fragment } from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +interface Props { + disabled?: boolean; + minute?: string; + minuteOptions: EuiSelectOption[]; + hour?: string; + hourOptions: EuiSelectOption[]; + onChange: ({ minute, hour }: { minute?: string; hour?: string }) => void; +} + +export const CronDaily: React.FunctionComponent = ({ + disabled, + minute, + minuteOptions, + hour, + hourOptions, + onChange, +}) => ( + + + } + fullWidth + data-test-subj="cronFrequencyConfiguration" + > + + + onChange({ hour: e.target.value })} + fullWidth + prepend={i18n.translate( + 'xpack.enterpriseSearch.cronEditor.cronDaily.fieldHour.textAtLabel', + { + defaultMessage: 'At', + } + )} + data-test-subj="cronFrequencyDailyHourSelect" + /> + + + + onChange({ minute: e.target.value })} + fullWidth + prepend=":" + data-test-subj="cronFrequencyDailyMinuteSelect" + /> + + + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_editor.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_editor.test.tsx new file mode 100644 index 00000000000000..5ab99c715453b3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_editor.test.tsx @@ -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 React from 'react'; + +import sinon from 'sinon'; + +import { findTestSubject } from '@elastic/eui/lib/test'; +import { Frequency } from '@kbn/es-ui-shared-plugin/public/components/cron_editor/types'; +import { mountWithI18nProvider } from '@kbn/test-jest-helpers'; + +import { CronEditor } from './cron_editor'; + +describe('CronEditor', () => { + ['MINUTE', 'HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR'].forEach((unit) => { + test(`is rendered with a ${unit} frequency`, () => { + const component = mountWithI18nProvider( + {}} + /> + ); + + expect(component).toMatchSnapshot(); + }); + }); + + describe('props', () => { + describe('frequencyBlockList', () => { + it('excludes the blocked frequencies from the frequency list', () => { + const component = mountWithI18nProvider( + {}} + /> + ); + + const frequencySelect = findTestSubject(component, 'cronFrequencySelect'); + expect(frequencySelect.text()).toBe('minutedaymonth'); + }); + }); + + describe('cronExpression', () => { + it('sets the values of the fields', () => { + const component = mountWithI18nProvider( + {}} + /> + ); + + const monthSelect = findTestSubject(component, 'cronFrequencyYearlyMonthSelect'); + expect(monthSelect.props().value).toBe('2'); + + const dateSelect = findTestSubject(component, 'cronFrequencyYearlyDateSelect'); + expect(dateSelect.props().value).toBe('5'); + + const hourSelect = findTestSubject(component, 'cronFrequencyYearlyHourSelect'); + expect(hourSelect.props().value).toBe('10'); + + const minuteSelect = findTestSubject(component, 'cronFrequencyYearlyMinuteSelect'); + expect(minuteSelect.props().value).toBe('20'); + }); + }); + + describe('onChange', () => { + it('is called when the frequency changes', () => { + const onChangeSpy = sinon.spy(); + const component = mountWithI18nProvider( + + ); + + const frequencySelect = findTestSubject(component, 'cronFrequencySelect'); + frequencySelect.simulate('change', { target: { value: 'MONTH' } }); + + sinon.assert.calledWith(onChangeSpy, { + cronExpression: '0 0 0 1 * ?', + fieldToPreferredValueMap: {}, + frequency: 'MONTH', + }); + }); + + it("is called when a field's value changes", () => { + const onChangeSpy = sinon.spy(); + const component = mountWithI18nProvider( + + ); + + const minuteSelect = findTestSubject(component, 'cronFrequencyYearlyMinuteSelect'); + minuteSelect.simulate('change', { target: { value: '40' } }); + + sinon.assert.calledWith(onChangeSpy, { + cronExpression: '0 40 * * * ?', + fieldToPreferredValueMap: { minute: '40' }, + frequency: 'YEAR', + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_editor.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_editor.tsx new file mode 100644 index 00000000000000..06e37aa2366f80 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_editor.tsx @@ -0,0 +1,253 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, { Component, Fragment } from 'react'; + +import { EuiSelect, EuiFormRow, EuiSelectOption } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { + MINUTE_OPTIONS, + HOUR_OPTIONS, + DAY_OPTIONS, + DATE_OPTIONS, + MONTH_OPTIONS, + UNITS, + frequencyToFieldsMap, + frequencyToBaselineFieldsMap, +} from './constants'; +import { CronDaily } from './cron_daily'; +import { CronHourly } from './cron_hourly'; +import { CronMonthly } from './cron_monthly'; +import { CronWeekly } from './cron_weekly'; +import { CronYearly } from './cron_yearly'; +import { cronExpressionToParts, cronPartsToExpression } from './services'; +import { Frequency, Field, FieldToValueMap } from './types'; + +const excludeBlockListedFrequencies = ( + units: EuiSelectOption[], + blockListedUnits: string[] = [] +): EuiSelectOption[] => { + if (blockListedUnits.length === 0) { + return units; + } + + return units.filter(({ value }) => !blockListedUnits.includes(value as string)); +}; + +interface Props { + frequencyBlockList?: string[]; + fieldToPreferredValueMap: FieldToValueMap; + frequency: Frequency; + cronExpression: string; + onChange: ({ + cronExpression, + fieldToPreferredValueMap, + frequency, + }: { + cronExpression: string; + fieldToPreferredValueMap: FieldToValueMap; + frequency: Frequency; + }) => void; + autoFocus?: boolean; + disabled?: boolean; +} + +type State = FieldToValueMap; + +export class CronEditor extends Component { + static getDerivedStateFromProps(props: Props) { + const { cronExpression } = props; + return cronExpressionToParts(cronExpression); + } + + constructor(props: Props) { + super(props); + + const { cronExpression } = props; + const parsedCron = cronExpressionToParts(cronExpression); + this.state = { + ...parsedCron, + }; + } + + onChangeFrequency = (frequency: Frequency) => { + const { onChange, fieldToPreferredValueMap } = this.props; + + // Update fields which aren't editable with acceptable baseline values. + const editableFields = Object.keys(frequencyToFieldsMap[frequency]) as Field[]; + const inheritedFields = editableFields.reduce( + (fieldBaselines, field) => { + if (fieldToPreferredValueMap[field] != null) { + fieldBaselines[field] = fieldToPreferredValueMap[field]; + } + return fieldBaselines; + }, + { ...frequencyToBaselineFieldsMap[frequency] } + ); + + const newCronExpression = cronPartsToExpression(inheritedFields); + + onChange({ + frequency, + cronExpression: newCronExpression, + fieldToPreferredValueMap, + }); + }; + + onChangeFields = (fields: FieldToValueMap) => { + const { onChange, frequency, fieldToPreferredValueMap } = this.props; + + const editableFields = Object.keys(frequencyToFieldsMap[frequency]) as Field[]; + const newFieldToPreferredValueMap: FieldToValueMap = {}; + + const editedFields = editableFields.reduce( + (accumFields, field) => { + if (fields[field] !== undefined) { + accumFields[field] = fields[field]; + // If the user changes a field's value, we want to maintain that value in the relevant + // field, even as the frequency field changes. For example, if the user selects "Monthly" + // frequency and changes the "Hour" field to "10", that field should still say "10" if the + // user changes the frequency to "Weekly". We'll support this UX by storing these values + // in the fieldToPreferredValueMap. + newFieldToPreferredValueMap[field] = fields[field]; + } else { + accumFields[field] = this.state[field]; + } + return accumFields; + }, + { ...frequencyToBaselineFieldsMap[frequency] } + ); + + const newCronExpression = cronPartsToExpression(editedFields); + + onChange({ + frequency, + cronExpression: newCronExpression, + fieldToPreferredValueMap: { + ...fieldToPreferredValueMap, + ...newFieldToPreferredValueMap, + }, + }); + }; + + renderForm() { + const { frequency, disabled } = this.props; + + const { minute, hour, day, date, month } = this.state; + + switch (frequency) { + case 'MINUTE': + return; + + case 'HOUR': + return ( + + ); + + case 'DAY': + return ( + + ); + + case 'WEEK': + return ( + + ); + + case 'MONTH': + return ( + + ); + + case 'YEAR': + return ( + + ); + + default: + return; + } + } + + render() { + const { disabled, frequency, frequencyBlockList } = this.props; + + return ( + + + } + fullWidth + > + ) => + this.onChangeFrequency(e.target.value as Frequency) + } + fullWidth + prepend={i18n.translate('xpack.enterpriseSearch.cronEditor.textEveryLabel', { + defaultMessage: 'Every', + })} + data-test-subj="cronFrequencySelect" + /> + + + {this.renderForm()} + + ); + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_hourly.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_hourly.tsx new file mode 100644 index 00000000000000..84ceab265e624d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_hourly.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 React, { Fragment } from 'react'; + +import { EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +interface Props { + disabled?: boolean; + minute?: string; + minuteOptions: EuiSelectOption[]; + onChange: ({ minute }: { minute?: string }) => void; +} + +export const CronHourly: React.FunctionComponent = ({ + disabled, + minute, + minuteOptions, + onChange, +}) => ( + + + } + fullWidth + data-test-subj="cronFrequencyConfiguration" + > + onChange({ minute: e.target.value })} + fullWidth + prepend={i18n.translate( + 'xpack.enterpriseSearch.cronEditor.cronHourly.fieldMinute.textAtLabel', + { + defaultMessage: 'At', + } + )} + data-test-subj="cronFrequencyHourlyMinuteSelect" + /> + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_monthly.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_monthly.tsx new file mode 100644 index 00000000000000..84867e5bbf8936 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_monthly.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, { Fragment } from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +interface Props { + disabled?: boolean; + minute?: string; + minuteOptions: EuiSelectOption[]; + hour?: string; + hourOptions: EuiSelectOption[]; + date?: string; + dateOptions: EuiSelectOption[]; + onChange: ({ minute, hour, date }: { minute?: string; hour?: string; date?: string }) => void; +} + +export const CronMonthly: React.FunctionComponent = ({ + disabled, + minute, + minuteOptions, + hour, + hourOptions, + date, + dateOptions, + onChange, +}) => ( + + + } + fullWidth + data-test-subj="cronFrequencyConfiguration" + > + onChange({ date: e.target.value })} + fullWidth + prepend={i18n.translate('xpack.enterpriseSearch.cronEditor.cronMonthly.textOnTheLabel', { + defaultMessage: 'On the', + })} + data-test-subj="cronFrequencyMonthlyDateSelect" + /> + + + + } + fullWidth + data-test-subj="cronFrequencyConfiguration" + > + + + onChange({ hour: e.target.value })} + fullWidth + prepend={i18n.translate( + 'xpack.enterpriseSearch.cronEditor.cronMonthly.fieldHour.textAtLabel', + { + defaultMessage: 'At', + } + )} + data-test-subj="cronFrequencyMonthlyHourSelect" + /> + + + + onChange({ minute: e.target.value })} + fullWidth + prepend=":" + data-test-subj="cronFrequencyMonthlyMinuteSelect" + /> + + + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_weekly.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_weekly.tsx new file mode 100644 index 00000000000000..83edd0ed0aaba4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_weekly.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, { Fragment } from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +interface Props { + disabled?: boolean; + minute?: string; + minuteOptions: EuiSelectOption[]; + hour?: string; + hourOptions: EuiSelectOption[]; + day?: string; + dayOptions: EuiSelectOption[]; + onChange: ({ minute, hour, day }: { minute?: string; hour?: string; day?: string }) => void; +} + +export const CronWeekly: React.FunctionComponent = ({ + disabled, + minute, + minuteOptions, + hour, + hourOptions, + day, + dayOptions, + onChange, +}) => ( + + + } + fullWidth + data-test-subj="cronFrequencyConfiguration" + > + onChange({ day: e.target.value })} + fullWidth + prepend={i18n.translate('xpack.enterpriseSearch.cronEditor.cronWeekly.textOnLabel', { + defaultMessage: 'On', + })} + data-test-subj="cronFrequencyWeeklyDaySelect" + /> + + + + } + fullWidth + data-test-subj="cronFrequencyConfiguration" + > + + + onChange({ hour: e.target.value })} + fullWidth + prepend={i18n.translate( + 'xpack.enterpriseSearch.cronEditor.cronWeekly.fieldHour.textAtLabel', + { + defaultMessage: 'At', + } + )} + data-test-subj="cronFrequencyWeeklyHourSelect" + /> + + + + onChange({ minute: e.target.value })} + aria-label={i18n.translate( + 'xpack.enterpriseSearch.cronEditor.cronWeekly.minuteSelectLabel', + { + defaultMessage: 'Minute', + } + )} + fullWidth + prepend=":" + data-test-subj="cronFrequencyWeeklyMinuteSelect" + /> + + + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_yearly.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_yearly.tsx new file mode 100644 index 00000000000000..158ab6dcac79d6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/cron_yearly.tsx @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSelectOption } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +interface Props { + disabled?: boolean; + minute?: string; + minuteOptions: EuiSelectOption[]; + hour?: string; + hourOptions: EuiSelectOption[]; + date?: string; + dateOptions: EuiSelectOption[]; + month?: string; + monthOptions: EuiSelectOption[]; + onChange: ({ + minute, + hour, + date, + month, + }: { + minute?: string; + hour?: string; + date?: string; + month?: string; + }) => void; +} + +export const CronYearly: React.FunctionComponent = ({ + disabled, + minute, + minuteOptions, + hour, + hourOptions, + date, + dateOptions, + month, + monthOptions, + onChange, +}) => ( + + + } + fullWidth + data-test-subj="cronFrequencyConfiguration" + > + onChange({ month: e.target.value })} + fullWidth + prepend={i18n.translate( + 'xpack.enterpriseSearch.cronEditor.cronYearly.fieldMonth.textInLabel', + { + defaultMessage: 'In', + } + )} + data-test-subj="cronFrequencyYearlyMonthSelect" + /> + + + + } + fullWidth + data-test-subj="cronFrequencyConfiguration" + > + onChange({ date: e.target.value })} + fullWidth + prepend={i18n.translate( + 'xpack.enterpriseSearch.cronEditor.cronYearly.fieldDate.textOnTheLabel', + { + defaultMessage: 'On the', + } + )} + data-test-subj="cronFrequencyYearlyDateSelect" + /> + + + + } + fullWidth + data-test-subj="cronFrequencyConfiguration" + > + + + onChange({ hour: e.target.value })} + fullWidth + prepend={i18n.translate( + 'xpack.enterpriseSearch.cronEditor.cronYearly.fieldHour.textAtLabel', + { + defaultMessage: 'At', + } + )} + data-test-subj="cronFrequencyYearlyHourSelect" + /> + + + + onChange({ minute: e.target.value })} + fullWidth + prepend=":" + data-test-subj="cronFrequencyYearlyMinuteSelect" + /> + + + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx new file mode 100644 index 00000000000000..f0e2d371dc0810 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/enterprise_search_cron_editor.tsx @@ -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 React, { useState } from 'react'; + +import { Frequency } from '@kbn/es-ui-shared-plugin/public/components/cron_editor/types'; + +import { Connector } from '../../../../common/types/connectors'; + +import { CronEditor } from './cron_editor'; + +interface Props { + disabled?: boolean; + onChange(scheduling: Connector['scheduling']): void; + scheduling: Connector['scheduling']; +} + +export const EnterpriseSearchCronEditor: React.FC = ({ disabled, onChange, scheduling }) => { + const [fieldToPreferredValueMap, setFieldToPreferredValueMap] = useState({}); + const [simpleCron, setSimpleCron] = useState<{ + expression: string; + frequency: Frequency; + }>({ + expression: scheduling?.interval ?? '', + frequency: scheduling?.interval ? cronToFrequency(scheduling.interval) : 'HOUR', + }); + + return ( + { + setSimpleCron({ + expression, + frequency, + }); + setFieldToPreferredValueMap(newFieldToPreferredValueMap); + onChange({ ...scheduling, interval: expression }); + }} + frequencyBlockList={['MINUTE']} + /> + ); +}; + +function cronToFrequency(cron: string): Frequency { + const fields = cron.split(' '); + if (fields.length < 4) { + return 'YEAR'; + } + if (fields[1] === '*') { + return 'MINUTE'; + } + if (fields[2] === '*') { + return 'HOUR'; + } + if (fields[3] === '*') { + return 'DAY'; + } + if (fields[4] === '?') { + return 'WEEK'; + } + if (fields[4] === '*') { + return 'MONTH'; + } + return 'YEAR'; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/index.ts new file mode 100644 index 00000000000000..981521acf886b5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/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 { CronEditor } from './cron_editor'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/readme.md b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/readme.md new file mode 100644 index 00000000000000..1b2f8e39e9e58c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/readme.md @@ -0,0 +1,5 @@ +`CronEditor` found `./cron_editor.tsx` is based on the `Cron Editor` component from `src/plugins/es_ui_shared/public/components/cron_editor` + +Includes a `disabled` prop that can be passed down to the child form components. + +TODO: PR this prop back to the original ES UI component diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/cron.ts b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/cron.ts new file mode 100644 index 00000000000000..542502fbcbe769 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/cron.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 { FieldToValueMap } from '../types'; + +export function cronExpressionToParts(expression: string): FieldToValueMap { + const parsedCron: FieldToValueMap = { + second: undefined, + minute: undefined, + hour: undefined, + day: undefined, + date: undefined, + month: undefined, + }; + + const parts = expression.split(' '); + + if (parts.length >= 1) { + parsedCron.second = parts[0]; + } + + if (parts.length >= 2) { + parsedCron.minute = parts[1]; + } + + if (parts.length >= 3) { + parsedCron.hour = parts[2]; + } + + if (parts.length >= 4) { + parsedCron.date = parts[3]; + } + + if (parts.length >= 5) { + parsedCron.month = parts[4]; + } + + if (parts.length >= 6) { + parsedCron.day = parts[5]; + } + + return parsedCron; +} + +export function cronPartsToExpression({ + second, + minute, + hour, + day, + date, + month, +}: FieldToValueMap): string { + return `${second} ${minute} ${hour} ${date} ${month} ${day}`; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/humanized_numbers.ts b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/humanized_numbers.ts new file mode 100644 index 00000000000000..e169a76ec8b419 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/humanized_numbers.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 type DayOrdinal = 0 | 1 | 2 | 3 | 4 | 5 | 6; +export type MonthOrdinal = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11; + +// The international ISO standard dictates Monday as the first day of the week, but cron patterns +// use Sunday as the first day, so we're going with the cron way. +const dayOrdinalToDayNameMap = { + 0: i18n.translate('xpack.enterpriseSearch.cronEditor.day.sunday', { defaultMessage: 'Sunday' }), + 1: i18n.translate('xpack.enterpriseSearch.cronEditor.day.monday', { defaultMessage: 'Monday' }), + 2: i18n.translate('xpack.enterpriseSearch.cronEditor.day.tuesday', { defaultMessage: 'Tuesday' }), + 3: i18n.translate('xpack.enterpriseSearch.cronEditor.day.wednesday', { + defaultMessage: 'Wednesday', + }), + 4: i18n.translate('xpack.enterpriseSearch.cronEditor.day.thursday', { + defaultMessage: 'Thursday', + }), + 5: i18n.translate('xpack.enterpriseSearch.cronEditor.day.friday', { defaultMessage: 'Friday' }), + 6: i18n.translate('xpack.enterpriseSearch.cronEditor.day.saturday', { + defaultMessage: 'Saturday', + }), +}; + +const monthOrdinalToMonthNameMap = { + 0: i18n.translate('xpack.enterpriseSearch.cronEditor.month.january', { + defaultMessage: 'January', + }), + 1: i18n.translate('xpack.enterpriseSearch.cronEditor.month.february', { + defaultMessage: 'February', + }), + 2: i18n.translate('xpack.enterpriseSearch.cronEditor.month.march', { defaultMessage: 'March' }), + 3: i18n.translate('xpack.enterpriseSearch.cronEditor.month.april', { defaultMessage: 'April' }), + 4: i18n.translate('xpack.enterpriseSearch.cronEditor.month.may', { defaultMessage: 'May' }), + 5: i18n.translate('xpack.enterpriseSearch.cronEditor.month.june', { defaultMessage: 'June' }), + 6: i18n.translate('xpack.enterpriseSearch.cronEditor.month.july', { defaultMessage: 'July' }), + 7: i18n.translate('xpack.enterpriseSearch.cronEditor.month.august', { defaultMessage: 'August' }), + 8: i18n.translate('xpack.enterpriseSearch.cronEditor.month.september', { + defaultMessage: 'September', + }), + 9: i18n.translate('xpack.enterpriseSearch.cronEditor.month.october', { + defaultMessage: 'October', + }), + 10: i18n.translate('xpack.enterpriseSearch.cronEditor.month.november', { + defaultMessage: 'November', + }), + 11: i18n.translate('xpack.enterpriseSearch.cronEditor.month.december', { + defaultMessage: 'December', + }), +}; + +export function getOrdinalValue(number: number): string { + // TODO: This is breaking reporting pdf generation. Possibly due to phantom not setting locale, + // which is needed by i18n (formatjs). Need to verify, fix, and restore i18n in place of static stings. + // return i18n.translate('xpack.enterpriseSearch.cronEditor.number.ordinal', { + // defaultMessage: '{number, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}', + // values: { number }, + // }); + // TODO: https://github.com/elastic/kibana/issues/27136 + + // Protects against falsey (including 0) values + const num = number && number.toString(); + const lastDigitString = num && num.substr(-1); + let ordinal; + + if (!lastDigitString) { + return number.toString(); + } + + const lastDigitNumeric = parseFloat(lastDigitString); + + switch (lastDigitNumeric) { + case 1: + ordinal = 'st'; + break; + case 2: + ordinal = 'nd'; + break; + case 3: + ordinal = 'rd'; + break; + default: + ordinal = 'th'; + } + + return `${num}${ordinal}`; +} + +export function getDayName(dayOrdinal: DayOrdinal): string { + return dayOrdinalToDayNameMap[dayOrdinal]; +} + +export function getMonthName(monthOrdinal: MonthOrdinal): string { + return monthOrdinalToMonthNameMap[monthOrdinal]; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/index.ts new file mode 100644 index 00000000000000..d8fcdd33822748 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/services/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { cronExpressionToParts, cronPartsToExpression } from './cron'; +export type { DayOrdinal, MonthOrdinal } from './humanized_numbers'; +export { getOrdinalValue, getDayName, getMonthName } from './humanized_numbers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/types.ts new file mode 100644 index 00000000000000..a7b2d7b5b63e77 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/cron_editor/types.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 type Frequency = 'MINUTE' | 'HOUR' | 'DAY' | 'WEEK' | 'MONTH' | 'YEAR'; +export type Field = 'second' | 'minute' | 'hour' | 'day' | 'date' | 'month'; +export type FieldToValueMap = { + [key in Field]?: string; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts index d06d9af1dbaf86..6766f0c7535d80 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts @@ -43,7 +43,7 @@ describe('fetchSyncJobs lib', () => { index: '.elastic-connectors-sync-jobs', query: { term: { - connector_id: 'id', + 'connector.id': 'id', }, }, size: 10, @@ -90,7 +90,7 @@ describe('fetchSyncJobs lib', () => { index: '.elastic-connectors-sync-jobs', query: { term: { - connector_id: 'id', + 'connector.id': 'id', }, }, size: 10, @@ -127,7 +127,7 @@ describe('fetchSyncJobs lib', () => { index: '.elastic-connectors-sync-jobs', query: { term: { - connector_id: 'id', + 'connector.id': 'id', }, }, size: 10, diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts index 25db360a66e71f..a4e2fe119eed56 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts @@ -39,7 +39,7 @@ export const fetchSyncJobsByConnectorId = async ( index: CONNECTORS_JOBS_INDEX, query: { term: { - connector_id: connectorId, + 'connector.id': connectorId, }, }, size, diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/document/get_document.ts b/x-pack/plugins/enterprise_search/server/lib/indices/document/get_document.ts new file mode 100644 index 00000000000000..a2d21d32fad317 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/indices/document/get_document.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 { GetResponse } from '@elastic/elasticsearch/lib/api/types'; +import { IScopedClusterClient } from '@kbn/core/server'; + +export const getDocument = async ( + client: IScopedClusterClient, + indexName: string, + documentId: string +): Promise> => { + const response = await client.asCurrentUser.get({ + id: documentId, + index: indexName, + }); + return response; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_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 index 4a2ba80ca43ab2..8bdbb0a3414427 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_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,7 +9,11 @@ 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 { + getMlModelTypesForModelConfig, + parseModelStateFromStats, + parseModelStateReasonFromStats, +} from '../../../../../../common/ml_inference_pipeline'; import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; import { getInferencePipelineNameFromIndexName } from '../../../../../utils/ml_inference_pipeline_utils'; @@ -139,27 +143,9 @@ export const getMlModelConfigsForModelIds = async ( trainedModelsStats.trained_model_stats.forEach((trainedModelStats) => { const trainedModelName = trainedModelStats.model_id; if (modelConfigs.hasOwnProperty(trainedModelName)) { - let modelState: TrainedModelState; - switch (trainedModelStats.deployment_stats?.state) { - case 'started': - modelState = TrainedModelState.Started; - break; - case 'starting': - modelState = TrainedModelState.Starting; - break; - case 'stopping': - modelState = TrainedModelState.Stopping; - break; - // @ts-ignore: type is wrong, "failed" is a possible state - case 'failed': - modelState = TrainedModelState.Failed; - break; - default: - modelState = TrainedModelState.NotDeployed; - break; - } - modelConfigs[trainedModelName].modelState = modelState; - modelConfigs[trainedModelName].modelStateReason = trainedModelStats.deployment_stats?.reason; + modelConfigs[trainedModelName].modelState = parseModelStateFromStats(trainedModelStats); + modelConfigs[trainedModelName].modelStateReason = + parseModelStateReasonFromStats(trainedModelStats); } }); diff --git a/x-pack/plugins/enterprise_search/server/lib/stats/get_sync_jobs.ts b/x-pack/plugins/enterprise_search/server/lib/stats/get_sync_jobs.ts new file mode 100644 index 00000000000000..99d62926eddd24 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/stats/get_sync_jobs.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; + +import { IScopedClusterClient } from '@kbn/core/server'; + +import { CONNECTORS_INDEX, CONNECTORS_JOBS_INDEX } from '../..'; +import { SyncJobsStats } from '../../../common/stats'; + +import { ConnectorStatus, SyncStatus } from '../../../common/types/connectors'; + +export const fetchSyncJobsStats = async (client: IScopedClusterClient): Promise => { + const connectorIdsResult = await client.asCurrentUser.search({ + index: CONNECTORS_INDEX, + scroll: '10s', + stored_fields: [], + }); + const ids = connectorIdsResult.hits.hits.map((hit) => hit._id); + const orphanedJobsCountResponse = await client.asCurrentUser.count({ + index: CONNECTORS_JOBS_INDEX, + query: { + terms: { + 'connector.id': ids, + }, + }, + }); + + const inProgressJobsCountResponse = await client.asCurrentUser.count({ + index: CONNECTORS_JOBS_INDEX, + query: { + term: { + status: SyncStatus.IN_PROGRESS, + }, + }, + }); + + const longRunningProgressJobsCountResponse = await client.asCurrentUser.count({ + index: CONNECTORS_JOBS_INDEX, + query: { + bool: { + filter: [ + { + term: { + status: SyncStatus.IN_PROGRESS, + }, + }, + { + range: { + last_seen: { + lt: moment().subtract(1, 'day').toISOString(), + }, + }, + }, + ], + }, + }, + }); + + const errorResponse = await client.asCurrentUser.count({ + index: CONNECTORS_JOBS_INDEX, + query: { + term: { + status: SyncStatus.ERROR, + }, + }, + }); + + const connectedResponse = await client.asCurrentUser.count({ + index: CONNECTORS_INDEX, + query: { + bool: { + filter: [ + { + term: { + status: ConnectorStatus.CONNECTED, + }, + }, + { + range: { + last_seen: { + gte: moment().subtract(30, 'minutes').toISOString(), + }, + }, + }, + ], + }, + }, + }); + + const incompleteResponse = await client.asCurrentUser.count({ + index: CONNECTORS_INDEX, + query: { + bool: { + should: [ + { + bool: { + must_not: { + terms: { + status: [ConnectorStatus.CONNECTED, ConnectorStatus.ERROR], + }, + }, + }, + }, + { + range: { + last_seen: { + gt: moment().subtract(30, 'minutes').toISOString(), + }, + }, + }, + ], + }, + }, + }); + + const response = { + connected: connectedResponse.count, + errors: errorResponse.count, + in_progress: inProgressJobsCountResponse.count, + incomplete: incompleteResponse.count, + long_running: longRunningProgressJobsCountResponse.count, + orphaned_jobs: orphanedJobsCountResponse.count, + }; + + return response; +}; diff --git a/x-pack/plugins/enterprise_search/server/plugin.ts b/x-pack/plugins/enterprise_search/server/plugin.ts index 436e4129581782..6634dd84733e3d 100644 --- a/x-pack/plugins/enterprise_search/server/plugin.ts +++ b/x-pack/plugins/enterprise_search/server/plugin.ts @@ -55,6 +55,7 @@ import { registerConfigDataRoute } from './routes/enterprise_search/config_data' import { registerConnectorRoutes } from './routes/enterprise_search/connectors'; import { registerCrawlerRoutes } from './routes/enterprise_search/crawler/crawler'; import { registerCreateAPIKeyRoute } from './routes/enterprise_search/create_api_key'; +import { registerStatsRoutes } from './routes/enterprise_search/stats'; import { registerTelemetryRoute } from './routes/enterprise_search/telemetry'; import { registerWorkplaceSearchRoutes } from './routes/workplace_search'; @@ -189,6 +190,7 @@ export class EnterpriseSearchPlugin implements Plugin { registerConnectorRoutes(dependencies); registerCrawlerRoutes(dependencies); registerAnalyticsRoutes(dependencies); + registerStatsRoutes(dependencies); getStartServices().then(([, { security: securityStart }]) => { registerCreateAPIKeyRoute(dependencies, securityStart); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.test.ts index 3947e569349c85..89c01bdd8fd7f0 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.test.ts @@ -594,7 +594,7 @@ describe('crawler routes', () => { it('validates correctly', () => { const request = { - body: { frequency: 7, unit: 'day' }, + body: { frequency: 7, unit: 'day', use_connector_schedule: true }, params: { indexName: 'index-name' }, }; mockRouter.shouldValidate(request); @@ -602,7 +602,7 @@ describe('crawler routes', () => { it('fails validation without a name param', () => { const request = { - body: { frequency: 7, unit: 'day' }, + body: { frequency: 7, unit: 'day', use_connector_schedule: true }, params: {}, }; mockRouter.shouldThrow(request); @@ -610,7 +610,7 @@ describe('crawler routes', () => { it('fails validation without a unit property in body', () => { const request = { - body: { frequency: 7 }, + body: { frequency: 7, use_connector_schedule: true }, params: { indexName: 'index-name' }, }; mockRouter.shouldThrow(request); @@ -618,7 +618,15 @@ describe('crawler routes', () => { it('fails validation without a frequency property in body', () => { const request = { - body: { unit: 'day' }, + body: { unit: 'day', use_connector_schedule: true }, + params: { indexName: 'index-name' }, + }; + mockRouter.shouldThrow(request); + }); + + it('fails validation without a use_connector_schedule property in body', () => { + const request = { + body: { frequency: 7, unit: 'day' }, params: { indexName: 'index-name' }, }; mockRouter.shouldThrow(request); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts index 0af97578b56a4f..c3b034f0b6ce7f 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler.ts @@ -363,6 +363,7 @@ export function registerCrawlerRoutes(routeDependencies: RouteDependencies) { body: schema.object({ frequency: schema.number(), unit: schema.string(), + use_connector_schedule: schema.boolean(), }), params: schema.object({ indexName: schema.string(), diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/documents.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/documents.ts new file mode 100644 index 00000000000000..47d36cfff07f5d --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/documents.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 { schema } from '@kbn/config-schema'; + +import { ErrorCode } from '../../../common/types/error_codes'; +import { getDocument } from '../../lib/indices/document/get_document'; +import { RouteDependencies } from '../../plugin'; +import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; +import { isNotFoundException } from '../../utils/identify_exceptions'; + +export function registerDocumentRoute({ router, log }: RouteDependencies) { + router.get( + { + path: '/internal/enterprise_search/indices/{index_name}/document/{document_id}', + validate: { + params: schema.object({ + document_id: schema.string(), + index_name: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const indexName = decodeURIComponent(request.params.index_name); + const documentId = decodeURIComponent(request.params.document_id); + const { client } = (await context.core).elasticsearch; + + try { + const documentResponse = await getDocument(client, indexName, documentId); + return response.ok({ + body: documentResponse, + headers: { 'content-type': 'application/json' }, + }); + } catch (error) { + if (isNotFoundException(error)) { + return response.customError({ + body: { + attributes: { + error_code: ErrorCode.DOCUMENT_NOT_FOUND, + }, + message: `Could not find document ${documentId}`, + }, + statusCode: 404, + }); + } else { + // otherwise, default handler + throw error; + } + } + }) + ); +} diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/index.ts index ea3a7f3805d3ff..1a609a22da8b8d 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/index.ts @@ -7,6 +7,7 @@ import { RouteDependencies } from '../../plugin'; +import { registerDocumentRoute } from './documents'; import { registerIndexRoutes } from './indices'; import { registerMappingRoute } from './mapping'; import { registerSearchRoute } from './search'; @@ -15,4 +16,5 @@ export const registerEnterpriseSearchRoutes = (dependencies: RouteDependencies) registerIndexRoutes(dependencies); registerMappingRoute(dependencies); registerSearchRoute(dependencies); + registerDocumentRoute(dependencies); }; 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 ea19e032c650e2..239310731583e4 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 @@ -577,12 +577,17 @@ export function registerIndexRoutes({ pipeline: { description: defaultDescription, ...pipeline }, }; - const simulateResult = await client.asCurrentUser.ingest.simulate(simulateRequest); + try { + const simulateResult = await client.asCurrentUser.ingest.simulate(simulateRequest); - return response.ok({ - body: simulateResult, - headers: { 'content-type': 'application/json' }, - }); + return response.ok({ + body: simulateResult, + headers: { 'content-type': 'application/json' }, + }); + } catch (e) { + log.error(`Error simulating inference pipeline: ${JSON.stringify(e)}`); + throw e; + } }) ); @@ -649,12 +654,17 @@ export function registerIndexRoutes({ pipeline: pipelinesResponse[pipelineName], }; - const simulateResult = await client.asCurrentUser.ingest.simulate(simulateRequest); + try { + const simulateResult = await client.asCurrentUser.ingest.simulate(simulateRequest); - return response.ok({ - body: simulateResult, - headers: { 'content-type': 'application/json' }, - }); + return response.ok({ + body: simulateResult, + headers: { 'content-type': 'application/json' }, + }); + } catch (e) { + log.error(`Error simulating inference pipeline: ${JSON.stringify(e)}`); + throw e; + } }) ); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/stats.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/stats.ts new file mode 100644 index 00000000000000..7053191ff6afbf --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/stats.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 { fetchSyncJobsStats } from '../../lib/stats/get_sync_jobs'; +import { RouteDependencies } from '../../plugin'; +import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; + +export function registerStatsRoutes({ router, log }: RouteDependencies) { + router.get( + { + path: '/internal/enterprise_search/stats/sync_jobs', + validate: {}, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { client } = (await context.core).elasticsearch; + const body = await fetchSyncJobsStats(client); + return response.ok({ body }); + }) + ); +} diff --git a/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts b/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts index 9577eabfd31f30..5874659c690f85 100644 --- a/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts +++ b/x-pack/plugins/enterprise_search/server/utils/identify_exceptions.ts @@ -33,3 +33,6 @@ export const isUnauthorizedException = (error: ElasticsearchResponseError) => export const isPipelineIsInUseException = (error: Error) => error.message === ErrorCode.PIPELINE_IS_IN_USE; + +export const isNotFoundException = (error: ElasticsearchResponseError) => + error.meta?.statusCode === 404; diff --git a/x-pack/plugins/fleet/common/constants/file_storage.ts b/x-pack/plugins/fleet/common/constants/file_storage.ts index a1988570a58737..24994a28c9128d 100644 --- a/x-pack/plugins/fleet/common/constants/file_storage.ts +++ b/x-pack/plugins/fleet/common/constants/file_storage.ts @@ -10,3 +10,12 @@ // found in `common/services/file_storage` export const FILE_STORAGE_METADATA_INDEX_PATTERN = '.fleet-files-*'; export const FILE_STORAGE_DATA_INDEX_PATTERN = '.fleet-file-data-*'; + +// which integrations support file upload and the name to use for the file upload index +export const FILE_STORAGE_INTEGRATION_INDEX_NAMES: Readonly> = { + elastic_agent: 'agent', + endpoint: 'endpoint', +}; +export const FILE_STORAGE_INTEGRATION_NAMES: Readonly = Object.keys( + FILE_STORAGE_INTEGRATION_INDEX_NAMES +); diff --git a/x-pack/plugins/fleet/common/experimental_features.ts b/x-pack/plugins/fleet/common/experimental_features.ts index 2c55ce4a2a08ec..e7379ba1e2a4bb 100644 --- a/x-pack/plugins/fleet/common/experimental_features.ts +++ b/x-pack/plugins/fleet/common/experimental_features.ts @@ -15,7 +15,7 @@ export const allowedExperimentalValues = Object.freeze({ createPackagePolicyMultiPageLayout: true, packageVerification: true, showDevtoolsRequest: true, - showRequestDiagnostics: false, + diagnosticFileUploadEnabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 72b3dddb7958ea..9b55cfdf7018d5 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -2127,6 +2127,109 @@ "operationId": "get-agent-tags" } }, + "/agents/{agentId}/request_diagnostics": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "agentId", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Agent - Request Diagnostics", + "tags": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "actionId": { + "type": "string" + } + } + } + } + } + } + }, + "operationId": "request-diagnostics-agent", + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ] + } + }, + "/agents/bulk_request_diagnostics": { + "post": { + "summary": "Agent - Bulk Request Diagnostics", + "tags": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "actionId": { + "type": "string" + } + } + } + } + } + } + }, + "operationId": "bulk-request-diagnostics", + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "batchSize": { + "type": "number" + }, + "agents": { + "oneOf": [ + { + "type": "string", + "description": "KQL query string, leave empty to action all agents" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "description": "list of agent IDs" + } + ] + } + }, + "required": [ + "agents" + ] + }, + "example": { + "agents": "fleet-agents.policy_id : (\"policy1\" or \"policy2\")" + } + } + } + } + } + }, "/agent_policies": { "get": { "summary": "Agent policies - List", diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 489e49fe30afa1..423f232d41f663 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -1314,6 +1314,66 @@ paths: schema: $ref: '#/components/schemas/get_agent_tags_response' operationId: get-agent-tags + /agents/{agentId}/request_diagnostics: + parameters: + - schema: + type: string + name: agentId + in: path + required: true + post: + summary: Agent - Request Diagnostics + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + actionId: + type: string + operationId: request-diagnostics-agent + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + /agents/bulk_request_diagnostics: + post: + summary: Agent - Bulk Request Diagnostics + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + actionId: + type: string + operationId: bulk-request-diagnostics + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + requestBody: + content: + application/json: + schema: + type: object + properties: + batchSize: + type: number + agents: + oneOf: + - type: string + description: KQL query string, leave empty to action all agents + - type: array + items: + type: string + description: list of agent IDs + required: + - agents + example: + agents: 'fleet-agents.policy_id : ("policy1" or "policy2")' /agent_policies: get: summary: Agent policies - List diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index bccfa64ff772d0..af7cb62b5c1f34 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -77,6 +77,10 @@ paths: $ref: 'paths/agents@bulk_update_tags.yaml' /agents/tags: $ref: paths/agent_tags.yaml + '/agents/{agentId}/request_diagnostics': + $ref: 'paths/agents@{agent_id}@request_diagnostics.yaml' + /agents/bulk_request_diagnostics: + $ref: 'paths/agents@bulk_request_diagnostics.yaml' # Agent policies endpoints /agent_policies: $ref: paths/agent_policies.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml new file mode 100644 index 00000000000000..ea76360c892f9f --- /dev/null +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@bulk_request_diagnostics.yaml @@ -0,0 +1,36 @@ +post: + summary: Agent - Bulk Request Diagnostics + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + actionId: + type: string + operationId: bulk-request-diagnostics + parameters: + - $ref: ../components/headers/kbn_xsrf.yaml + requestBody: + content: + application/json: + schema: + type: object + properties: + batchSize: + type: number + agents: + oneOf: + - type: string + description: KQL query string, leave empty to action all agents + - type: array + items: + type: string + description: list of agent IDs + required: + - agents + example: + agents: "fleet-agents.policy_id : (\"policy1\" or \"policy2\")" diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml new file mode 100644 index 00000000000000..574ca0bbf918cd --- /dev/null +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@request_diagnostics.yaml @@ -0,0 +1,23 @@ +parameters: + - schema: + type: string + name: agentId + in: path + required: true +post: + summary: Agent - Request Diagnostics + tags: [] + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + actionId: + type: string + operationId: request-diagnostics-agent + parameters: + - $ref: ../components/headers/kbn_xsrf.yaml + diff --git a/x-pack/plugins/fleet/common/services/file_storage.ts b/x-pack/plugins/fleet/common/services/file_storage.ts index 4c5e9ac204c58f..afabb421e7d454 100644 --- a/x-pack/plugins/fleet/common/services/file_storage.ts +++ b/x-pack/plugins/fleet/common/services/file_storage.ts @@ -34,6 +34,11 @@ export const getFileDataIndexName = (integrationName: string): string => { ); }; +/** + * Returns the write index name for a given file upload alias name, this is the same for metadata and chunks + * @param aliasName + */ +export const getFileWriteIndexName = (aliasName: string) => aliasName + '-000001'; /** * Returns back the integration name for a given File Data (chunks) index name. * @@ -63,3 +68,15 @@ export const getIntegrationNameFromFileDataIndexName = (indexName: string): stri throw new Error(`Index name ${indexName} does not seem to be a File Data storage index`); }; + +export const getFileStorageWriteIndexBody = (aliasName: string) => ({ + aliases: { + [aliasName]: { + is_write_index: true, + }, + }, + settings: { + 'index.lifecycle.rollover_alias': aliasName, + 'index.hidden': true, + }, +}); diff --git a/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts index 7d5a7966f91c30..e60da12d924cc8 100644 --- a/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts +++ b/x-pack/plugins/fleet/common/services/simplified_package_policy_helper.test.ts @@ -131,6 +131,7 @@ describe('toPackagePolicy', () => { data_stream: 'logs-nginx.access', features: { synthetic_source: true, + tsdb: false, }, }, ], @@ -142,6 +143,7 @@ describe('toPackagePolicy', () => { data_stream: 'logs-nginx.access', features: { synthetic_source: true, + tsdb: false, }, }, ]); diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 4ef4a7fdd751bc..ca7cc3200da4bc 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -352,6 +352,7 @@ export interface RegistryElasticsearch { 'index_template.mappings'?: estypes.MappingTypeMapping; 'ingest_pipeline.name'?: string; source_mode?: 'default' | 'synthetic'; + index_mode?: 'time_series'; } export interface RegistryDataStreamPrivileges { @@ -444,7 +445,7 @@ export type PackageInfo = | Installable>; // TODO - Expand this with other experimental indexing types -export type ExperimentalIndexingFeature = 'synthetic_source'; +export type ExperimentalIndexingFeature = 'synthetic_source' | 'tsdb'; export interface ExperimentalDataStreamFeature { data_stream: string; diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts index 629250c243cea8..0a951c0de8fcea 100644 --- a/x-pack/plugins/fleet/common/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts @@ -33,6 +33,7 @@ export interface NewPackagePolicyInputStream { privileges?: { indices?: string[]; }; + index_mode?: string; }; }; release?: RegistryRelease; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx index 1d0cda70edea4e..7bcda09d08d8fd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx @@ -19,6 +19,7 @@ import { EuiButtonEmpty, } from '@elastic/eui'; +import { getRegistryDataStreamAssetBaseName } from '../../../../../../../../../common/services'; import type { NewPackagePolicy, NewPackagePolicyInput, @@ -125,6 +126,41 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ [packageInputStreams, packagePolicyInput.streams] ); + // setting Indexing setting: TSDB to enabled by default, if the data stream's index_mode is set to time_series + let isUpdated = false; + inputStreams.forEach(({ packagePolicyInputStream }) => { + const dataStreamInfo = packageInfo.data_streams?.find( + (ds) => ds.dataset === packagePolicyInputStream?.data_stream.dataset + ); + + if (dataStreamInfo?.elasticsearch?.index_mode === 'time_series') { + if (!packagePolicy.package) return; + if (!packagePolicy.package?.experimental_data_stream_features) + packagePolicy.package!.experimental_data_stream_features = []; + + const dsName = getRegistryDataStreamAssetBaseName(packagePolicyInputStream!.data_stream); + const match = packagePolicy.package!.experimental_data_stream_features.find( + (feat) => feat.data_stream === dsName + ); + if (match) { + if (!match.features.tsdb) { + match.features.tsdb = true; + isUpdated = true; + } + } else { + packagePolicy.package!.experimental_data_stream_features.push({ + data_stream: dsName, + features: { tsdb: true, synthetic_source: false }, + }); + isUpdated = true; + } + } + }); + + if (isUpdated) { + updatePackagePolicy(packagePolicy); + } + return ( <> {/* Header / input-level toggle */} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx index 54b6205da2ed9c..2e5f98c8359e77 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, Fragment, memo, useMemo, useEffect, useRef } from 'react'; +import React, { useState, Fragment, memo, useMemo, useEffect, useRef, useCallback } from 'react'; import ReactMarkdown from 'react-markdown'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -18,13 +18,14 @@ import { EuiSpacer, EuiButtonEmpty, EuiTitle, + EuiToolTip, } from '@elastic/eui'; import { useRouteMatch } from 'react-router-dom'; import { mapPackageReleaseToIntegrationCardRelease } from '../../../../../../../../services/package_prerelease'; import { getRegistryDataStreamAssetBaseName } from '../../../../../../../../../common/services'; - +import type { ExperimentalIndexingFeature } from '../../../../../../../../../common/types/models/epm'; import type { NewPackagePolicy, NewPackagePolicyInputStream, @@ -120,6 +121,62 @@ export const PackagePolicyInputStreamConfig = memo( [advancedVars, inputStreamValidationResults?.vars] ); + const isFeatureEnabled = useCallback( + (feature: ExperimentalIndexingFeature) => + packagePolicy.package?.experimental_data_stream_features?.some( + ({ data_stream: dataStream, features }) => + dataStream === + getRegistryDataStreamAssetBaseName(packagePolicyInputStream.data_stream) && + features[feature] + ) ?? false, + [ + packagePolicy.package?.experimental_data_stream_features, + packagePolicyInputStream.data_stream, + ] + ); + + const newExperimentalIndexingFeature = { + synthetic_source: isFeatureEnabled('synthetic_source'), + tsdb: isFeatureEnabled('tsdb'), + }; + + const onIndexingSettingChange = ( + features: Partial> + ) => { + if (!packagePolicy.package) { + return; + } + + const newExperimentalDataStreamFeatures = [ + ...(packagePolicy.package.experimental_data_stream_features ?? []), + ]; + + const dataStream = getRegistryDataStreamAssetBaseName(packagePolicyInputStream.data_stream); + + const existingSettingRecord = newExperimentalDataStreamFeatures.find( + (x) => x.data_stream === dataStream + ); + + if (existingSettingRecord) { + existingSettingRecord.features = { + ...existingSettingRecord.features, + ...features, + }; + } else { + newExperimentalDataStreamFeatures.push({ + data_stream: dataStream, + features: { ...newExperimentalIndexingFeature, ...features }, + }); + } + + updatePackagePolicy({ + package: { + ...packagePolicy.package, + experimental_data_stream_features: newExperimentalDataStreamFeatures, + }, + }); + }; + return ( <> @@ -311,15 +368,7 @@ export const PackagePolicyInputStreamConfig = memo( - dataStream === - getRegistryDataStreamAssetBaseName( - packagePolicyInputStream.data_stream - ) && features.synthetic_source - ) ?? false - } + checked={isFeatureEnabled('synthetic_source')} label={ ( /> } onChange={(e) => { - if (!packagePolicy.package) { - return; - } - - const newExperimentalDataStreamFeatures = [ - ...(packagePolicy.package.experimental_data_stream_features ?? []), - ]; - - const dataStream = getRegistryDataStreamAssetBaseName( - packagePolicyInputStream.data_stream - ); - - const existingSettingRecord = newExperimentalDataStreamFeatures.find( - (x) => x.data_stream === dataStream - ); - - if (existingSettingRecord) { - existingSettingRecord.features.synthetic_source = e.target.checked; - } else { - newExperimentalDataStreamFeatures.push({ - data_stream: dataStream, - features: { - synthetic_source: e.target.checked, - }, - }); - } - - updatePackagePolicy({ - package: { - ...packagePolicy.package, - experimental_data_stream_features: - newExperimentalDataStreamFeatures, - }, + onIndexingSettingChange({ + synthetic_source: e.target.checked, }); }} /> + + + } + > + + } + onChange={(e) => { + onIndexingSettingChange({ + tsdb: e.target.checked, + }); + }} + /> + + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_managed.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_managed.tsx index aa3b0830aa2006..68f78017c962cd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_managed.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_managed.tsx @@ -19,9 +19,10 @@ import { } from '../../../../../../../../../components/agent_enrollment_flyout/steps'; import { ManualInstructions } from '../../../../../../../../../components/enrollment_instructions'; -import type { InstallAgentPageProps } from './types'; import { KubernetesManifestApplyStep } from '../../../../../../../../../components/agent_enrollment_flyout/steps/run_k8s_apply_command_step'; +import type { InstallAgentPageProps } from './types'; + export const InstallElasticAgentManagedPageStep: React.FC = (props) => { const { cancelUrl, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx index b52a0401b86506..76bcd21c0207cc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/actions_menu.tsx @@ -38,7 +38,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ const isUnenrolling = agent.status === 'unenrolling'; const hasFleetServer = agentPolicy && policyHasFleetServer(agentPolicy); - const { showRequestDiagnostics } = ExperimentalFeaturesService.get(); + const { diagnosticFileUploadEnabled } = ExperimentalFeaturesService.get(); const onClose = useMemo(() => { if (onCancelReassign) { @@ -95,7 +95,7 @@ export const AgentDetailsActionMenu: React.FunctionComponent<{ , ]; - if (showRequestDiagnostics) { + if (diagnosticFileUploadEnabled) { menuItems.push( { navigateToApp(routeState.onDoneNavigateTo[0], routeState.onDoneNavigateTo[1]); } }, [routeState, navigateToApp]); - const { showRequestDiagnostics } = ExperimentalFeaturesService.get(); + const { diagnosticFileUploadEnabled } = ExperimentalFeaturesService.get(); const host = agentData?.item?.local_metadata?.host; @@ -156,7 +156,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => { isSelected: tabId === 'logs', }, ]; - if (showRequestDiagnostics) { + if (diagnosticFileUploadEnabled) { tabs.push({ id: 'diagnostics', name: i18n.translate('xpack.fleet.agentDetails.subTabs.diagnosticsTab', { @@ -167,7 +167,7 @@ export const AgentDetailsPage: React.FunctionComponent = () => { }); } return tabs; - }, [getHref, agentId, tabId, showRequestDiagnostics]); + }, [getHref, agentId, tabId, diagnosticFileUploadEnabled]); return ( = ({ const agentCount = selectionMode === 'manual' ? selectedAgents.length : totalActiveAgents; const agents = selectionMode === 'manual' ? selectedAgents : currentQuery; const [tagsPopoverButton, setTagsPopoverButton] = useState(); - const { showRequestDiagnostics } = ExperimentalFeaturesService.get(); + const { diagnosticFileUploadEnabled } = ExperimentalFeaturesService.get(); const menuItems = [ { @@ -171,7 +171,7 @@ export const AgentBulkActions: React.FunctionComponent = ({ }, ]; - if (showRequestDiagnostics) { + if (diagnosticFileUploadEnabled) { menuItems.push({ name: ( { createPackagePolicyMultiPageLayout: true, packageVerification: true, showDevtoolsRequest: false, - showRequestDiagnostics: false, + diagnosticFileUploadEnabled: false, }); }); it('should show no Actions button when no agent is selected', async () => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx index ad9c9bdbf3f7ba..6fdf4016d457f6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx @@ -38,7 +38,7 @@ export const TableRowActions: React.FunctionComponent<{ const isUnenrolling = agent.status === 'unenrolling'; const kibanaVersion = useKibanaVersion(); const [isMenuOpen, setIsMenuOpen] = useState(false); - const { showRequestDiagnostics } = ExperimentalFeaturesService.get(); + const { diagnosticFileUploadEnabled } = ExperimentalFeaturesService.get(); const menuItems = [ ); - if (showRequestDiagnostics) { + if (diagnosticFileUploadEnabled) { menuItems.push( { - + }> + + diff --git a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx index f4cd7f9403280f..9a6f48132e0a67 100644 --- a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx +++ b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx @@ -68,7 +68,7 @@ export const createFleetTestRendererMock = (): TestRenderer => { createPackagePolicyMultiPageLayout: true, packageVerification: true, showDevtoolsRequest: false, - showRequestDiagnostics: false, + diagnosticFileUploadEnabled: false, }); const HookWrapper = memo(({ children }) => { diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index e38e6c966c11db..f2ee5b034bf955 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -284,6 +284,7 @@ const getSavedObjectTypes = ( type: 'nested', properties: { synthetic_source: { type: 'boolean' }, + tsdb: { type: 'boolean' }, }, }, }, diff --git a/x-pack/plugins/fleet/server/services/epm/archive/parse.test.ts b/x-pack/plugins/fleet/server/services/epm/archive/parse.test.ts index e4ee10c4e270cc..32393eebd12e5d 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/parse.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/parse.test.ts @@ -67,6 +67,11 @@ describe('parseDataStreamElasticsearchEntry', () => { source_mode: 'synthetic', }); }); + it('Should add index_mode', () => { + expect(parseDataStreamElasticsearchEntry({ index_mode: 'time_series' })).toEqual({ + index_mode: 'time_series', + }); + }); it('Should add index_template mappings and expand dots', () => { expect( parseDataStreamElasticsearchEntry({ diff --git a/x-pack/plugins/fleet/server/services/epm/archive/parse.ts b/x-pack/plugins/fleet/server/services/epm/archive/parse.ts index d4ee87dc232f00..1007b1fe353445 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/parse.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/parse.ts @@ -539,6 +539,10 @@ export function parseDataStreamElasticsearchEntry( ); } + if (expandedElasticsearch?.index_mode) { + parsedElasticsearchEntry.index_mode = expandedElasticsearch.index_mode; + } + return parsedElasticsearchEntry; } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 14a773cfd94bf9..ca03d272405f4f 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -9,8 +9,21 @@ import { merge, concat, uniqBy, omit } from 'lodash'; import Boom from '@hapi/boom'; import type { ElasticsearchClient, Logger } from '@kbn/core/server'; +import type { IndicesCreateRequest } from '@elastic/elasticsearch/lib/api/types'; + +import { + FILE_STORAGE_INTEGRATION_INDEX_NAMES, + FILE_STORAGE_INTEGRATION_NAMES, +} from '../../../../../common/constants'; + import { ElasticsearchAssetType } from '../../../../types'; -import { getPipelineNameForDatastream } from '../../../../../common/services'; +import { + getFileWriteIndexName, + getFileStorageWriteIndexBody, + getPipelineNameForDatastream, + getFileDataIndexName, + getFileMetadataIndexName, +} from '../../../../../common/services'; import type { RegistryDataStream, IndexTemplateEntry, @@ -341,6 +354,43 @@ export async function ensureDefaultComponentTemplates( ); } +/* + * Given a list of integration names, if the integrations support file upload + * then ensure that the alias has a matching write index, as we use "plain" indices + * not data streams. + * e.g .fleet-file-data-agent must have .fleet-file-data-agent-00001 as the write index + * before files can be uploaded. + */ +export async function ensureFileUploadWriteIndices(opts: { + esClient: ElasticsearchClient; + logger: Logger; + integrationNames: string[]; +}) { + const { esClient, logger, integrationNames } = opts; + + const integrationsWithFileUpload = integrationNames.filter((integration) => + FILE_STORAGE_INTEGRATION_NAMES.includes(integration as any) + ); + + if (!integrationsWithFileUpload.length) return []; + + const ensure = (aliasName: string) => + ensureAliasHasWriteIndex({ + esClient, + logger, + aliasName, + writeIndexName: getFileWriteIndexName(aliasName), + body: getFileStorageWriteIndexBody(aliasName), + }); + + return Promise.all( + integrationsWithFileUpload.flatMap((integrationName) => { + const indexName = FILE_STORAGE_INTEGRATION_INDEX_NAMES[integrationName]; + return [ensure(getFileDataIndexName(indexName)), ensure(getFileMetadataIndexName(indexName))]; + }) + ); +} + export async function ensureComponentTemplate( esClient: ElasticsearchClient, logger: Logger, @@ -371,6 +421,37 @@ export async function ensureComponentTemplate( return { isCreated: !existingTemplate }; } +export async function ensureAliasHasWriteIndex(opts: { + esClient: ElasticsearchClient; + logger: Logger; + aliasName: string; + writeIndexName: string; + body: Omit; +}): Promise { + const { esClient, logger, aliasName, writeIndexName, body } = opts; + const existingIndex = await retryTransientEsErrors( + () => + esClient.indices.exists( + { + index: [aliasName], + }, + { + ignore: [404], + } + ), + { logger } + ); + + if (!existingIndex) { + await retryTransientEsErrors( + () => esClient.indices.create({ index: writeIndexName, ...body }, { ignore: [404] }), + { + logger, + } + ); + } +} + export function prepareTemplate({ pkg, dataStream, 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 78683ecd07e0a7..4b0aed34f8520c 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 @@ -32,7 +32,10 @@ import type { PackageAssetReference, PackageVerificationResult, } from '../../../types'; -import { prepareToInstallTemplates } from '../elasticsearch/template/install'; +import { + ensureFileUploadWriteIndices, + prepareToInstallTemplates, +} from '../elasticsearch/template/install'; import { removeLegacyTemplates } from '../elasticsearch/template/remove_legacy'; import { prepareToInstallPipelines, @@ -47,7 +50,7 @@ import { installMlModel } from '../elasticsearch/ml_model'; import { installIlmForDataStream } from '../elasticsearch/datastream_ilm/install'; import { saveArchiveEntries } from '../archive/storage'; import { ConcurrentInstallOperationError } from '../../../errors'; -import { packagePolicyService } from '../..'; +import { appContextService, packagePolicyService } from '../..'; import { createInstallation, updateEsAssetReferences, restartInstallation } from './install'; import { withPackageSpan } from './utils'; @@ -214,6 +217,15 @@ export async function _installPackage({ logger.warn(`Error removing legacy templates: ${e.message}`); } + const { diagnosticFileUploadEnabled } = appContextService.getExperimentalFeatures(); + if (diagnosticFileUploadEnabled) { + await ensureFileUploadWriteIndices({ + integrationNames: [packageInfo.name], + esClient, + logger, + }); + } + // update current backing indices of each data stream await withPackageSpan('Update write indices', () => updateCurrentWriteIndices(esClient, logger, installedTemplates) diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 3c4861b563b08b..82c3175583a75d 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -330,6 +330,18 @@ export async function getInstallationObject(options: { }); } +export async function getInstallationObjects(options: { + savedObjectsClient: SavedObjectsClientContract; + pkgNames: string[]; +}) { + const { savedObjectsClient, pkgNames } = options; + const res = await savedObjectsClient.bulkGet( + pkgNames.map((pkgName) => ({ id: pkgName, type: PACKAGES_SAVED_OBJECT_TYPE })) + ); + + return res.saved_objects.filter((so) => so?.attributes); +} + export async function getInstallation(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; @@ -339,6 +351,14 @@ export async function getInstallation(options: { return savedObject?.attributes; } +export async function getInstallationsByName(options: { + savedObjectsClient: SavedObjectsClientContract; + pkgNames: string[]; +}) { + const savedObjects = await getInstallationObjects(options); + return savedObjects.map((so) => so.attributes); +} + function sortByName(a: { name: string }, b: { name: string }) { if (a.name > b.name) { return 1; diff --git a/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.test.ts b/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.test.ts index 8c7afa5d30fed9..dddd11dd6ee19a 100644 --- a/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.test.ts @@ -10,12 +10,17 @@ import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks import type { NewPackagePolicy, PackagePolicy } from '../../types'; -import { handleExperimentalDatastreamFeatureOptIn } from './experimental_datastream_features'; +import { + builRoutingPath, + handleExperimentalDatastreamFeatureOptIn, +} from './experimental_datastream_features'; function getNewTestPackagePolicy({ isSyntheticSourceEnabled, + isTSDBEnabled, }: { isSyntheticSourceEnabled: boolean; + isTSDBEnabled: boolean; }): NewPackagePolicy { const packagePolicy: NewPackagePolicy = { name: 'Test policy', @@ -33,6 +38,7 @@ function getNewTestPackagePolicy({ data_stream: 'metrics-test.test', features: { synthetic_source: isSyntheticSourceEnabled, + tsdb: isTSDBEnabled, }, }, ], @@ -44,8 +50,10 @@ function getNewTestPackagePolicy({ function getExistingTestPackagePolicy({ isSyntheticSourceEnabled, + isTSDBEnabled, }: { isSyntheticSourceEnabled: boolean; + isTSDBEnabled: boolean; }): PackagePolicy { const packagePolicy: PackagePolicy = { id: 'test-policy', @@ -64,6 +72,7 @@ function getExistingTestPackagePolicy({ data_stream: 'metrics-test.test', features: { synthetic_source: isSyntheticSourceEnabled, + tsdb: isTSDBEnabled, }, }, ], @@ -83,40 +92,89 @@ describe('experimental_datastream_features', () => { soClient.get.mockClear(); esClient.cluster.getComponentTemplate.mockClear(); esClient.cluster.putComponentTemplate.mockClear(); + + esClient.cluster.getComponentTemplate.mockResolvedValueOnce({ + component_templates: [ + { + name: 'metrics-test.test@package', + component_template: { + template: { + settings: {}, + mappings: { + _source: { + // @ts-expect-error + mode: 'stored', + }, + properties: { + test_dimension: { + type: 'keyword', + time_series_dimension: true, + }, + }, + }, + }, + }, + }, + ], + }); }); const soClient = savedObjectsClientMock.create(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; describe('when package policy does not exist (create)', () => { - it('updates component template', async () => { - const packagePolicy = getNewTestPackagePolicy({ isSyntheticSourceEnabled: true }); - + beforeEach(() => { soClient.get.mockResolvedValueOnce({ attributes: { experimental_data_stream_features: [ - { data_stream: 'metrics-test.test', features: { synthetic_source: false } }, + { + data_stream: 'metrics-test.test', + features: { synthetic_source: false, tsdb: false }, + }, ], }, id: 'mocked', type: 'mocked', references: [], }); + }); + it('updates component template', async () => { + const packagePolicy = getNewTestPackagePolicy({ + isSyntheticSourceEnabled: true, + isTSDBEnabled: false, + }); - esClient.cluster.getComponentTemplate.mockResolvedValueOnce({ - component_templates: [ + await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy }); + + expect(esClient.cluster.getComponentTemplate).toHaveBeenCalled(); + expect(esClient.cluster.putComponentTemplate).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + template: expect.objectContaining({ + mappings: expect.objectContaining({ _source: { mode: 'synthetic' } }), + }), + }), + }) + ); + }); + + it('should update index template', async () => { + const packagePolicy = getNewTestPackagePolicy({ + isSyntheticSourceEnabled: false, + isTSDBEnabled: true, + }); + + esClient.indices.getIndexTemplate.mockResolvedValueOnce({ + index_templates: [ { - name: 'metrics-test.test@package', - component_template: { + name: 'metrics-test.test', + index_template: { template: { settings: {}, - mappings: { - _source: { - // @ts-expect-error - mode: 'stored', - }, - }, + mappings: {}, }, + composed_of: [], + index_patterns: '', }, }, ], @@ -124,12 +182,14 @@ describe('experimental_datastream_features', () => { await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy }); - expect(esClient.cluster.getComponentTemplate).toHaveBeenCalled(); - expect(esClient.cluster.putComponentTemplate).toHaveBeenCalledWith( + expect(esClient.indices.getIndexTemplate).toHaveBeenCalled(); + expect(esClient.indices.putIndexTemplate).toHaveBeenCalledWith( expect.objectContaining({ body: expect.objectContaining({ template: expect.objectContaining({ - mappings: expect.objectContaining({ _source: { mode: 'synthetic' } }), + settings: expect.objectContaining({ + index: { mode: 'time_series', routing_path: ['test_dimension'] }, + }), }), }), }) @@ -140,12 +200,18 @@ describe('experimental_datastream_features', () => { describe('when package policy exists (update)', () => { describe('when opt in status in unchanged', () => { it('does not update component template', async () => { - const packagePolicy = getExistingTestPackagePolicy({ isSyntheticSourceEnabled: true }); + const packagePolicy = getExistingTestPackagePolicy({ + isSyntheticSourceEnabled: true, + isTSDBEnabled: false, + }); soClient.get.mockResolvedValueOnce({ attributes: { experimental_data_stream_features: [ - { data_stream: 'metrics-test.test', features: { synthetic_source: true } }, + { + data_stream: 'metrics-test.test', + features: { synthetic_source: true, tsdb: false }, + }, ], }, id: 'mocked', @@ -161,34 +227,58 @@ describe('experimental_datastream_features', () => { }); describe('when opt in status is changed', () => { - it('updates component template', async () => { - const packagePolicy = getExistingTestPackagePolicy({ isSyntheticSourceEnabled: true }); - + beforeEach(() => { soClient.get.mockResolvedValueOnce({ attributes: { experimental_data_stream_features: [ - { data_stream: 'metrics-test.test', features: { synthetic_source: false } }, + { + data_stream: 'metrics-test.test', + features: { synthetic_source: false, tsdb: false }, + }, ], }, id: 'mocked', type: 'mocked', references: [], }); + }); + it('updates component template', async () => { + const packagePolicy = getExistingTestPackagePolicy({ + isSyntheticSourceEnabled: true, + isTSDBEnabled: false, + }); + + await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy }); + + expect(esClient.cluster.getComponentTemplate).toHaveBeenCalled(); + expect(esClient.cluster.putComponentTemplate).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + template: expect.objectContaining({ + mappings: expect.objectContaining({ _source: { mode: 'synthetic' } }), + }), + }), + }) + ); + }); + + it('should update index template', async () => { + const packagePolicy = getExistingTestPackagePolicy({ + isSyntheticSourceEnabled: false, + isTSDBEnabled: true, + }); - esClient.cluster.getComponentTemplate.mockResolvedValueOnce({ - component_templates: [ + esClient.indices.getIndexTemplate.mockResolvedValueOnce({ + index_templates: [ { - name: 'metrics-test.test@package', - component_template: { + name: 'metrics-test.test', + index_template: { template: { settings: {}, - mappings: { - _source: { - // @ts-expect-error - mode: 'stored', - }, - }, + mappings: {}, }, + composed_of: [], + index_patterns: '', }, }, ], @@ -196,12 +286,14 @@ describe('experimental_datastream_features', () => { await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy }); - expect(esClient.cluster.getComponentTemplate).toHaveBeenCalled(); - expect(esClient.cluster.putComponentTemplate).toHaveBeenCalledWith( + expect(esClient.indices.getIndexTemplate).toHaveBeenCalled(); + expect(esClient.indices.putIndexTemplate).toHaveBeenCalledWith( expect.objectContaining({ body: expect.objectContaining({ template: expect.objectContaining({ - mappings: expect.objectContaining({ _source: { mode: 'synthetic' } }), + settings: expect.objectContaining({ + index: { mode: 'time_series', routing_path: ['test_dimension'] }, + }), }), }), }) @@ -209,4 +301,64 @@ describe('experimental_datastream_features', () => { }); }); }); + it('should build routing path', () => { + const mappingProperties = { + cloud: { + properties: { + availability_zone: { + ignore_above: 1024, + type: 'keyword', + }, + image: { + properties: { + id: { + ignore_above: 1024, + type: 'keyword', + }, + }, + }, + }, + }, + test_dimension: { + time_series_dimension: true, + type: 'keyword', + }, + '@timestamp': { + type: 'date', + }, + }; + const routingPath = builRoutingPath(mappingProperties as any); + expect(routingPath).toEqual(['test_dimension']); + }); + + it('should build routing path from nested properties', () => { + const mappingProperties = { + cloud: { + properties: { + availability_zone: { + ignore_above: 1024, + type: 'keyword', + }, + image: { + properties: { + id: { + ignore_above: 1024, + type: 'keyword', + time_series_dimension: true, + }, + }, + }, + }, + }, + test_dimension: { + time_series_dimension: true, + type: 'keyword', + }, + '@timestamp': { + type: 'date', + }, + }; + const routingPath = builRoutingPath(mappingProperties as any); + expect(routingPath).toEqual(['cloud.image.id', 'test_dimension']); + }); }); diff --git a/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts b/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts index 2b8b05aed89c37..3a60733a57eb50 100644 --- a/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts +++ b/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts @@ -5,6 +5,10 @@ * 2.0. */ +import type { + MappingProperty, + PropertyName, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; @@ -12,6 +16,31 @@ import type { NewPackagePolicy, PackagePolicy } from '../../types'; import { getInstallation } from '../epm/packages'; import { updateDatastreamExperimentalFeatures } from '../epm/packages/update'; +function mapFields(mappingProperties: Record) { + const mappings = Object.keys(mappingProperties).reduce((acc, curr) => { + const property = mappingProperties[curr] as any; + if (property.properties) { + const childMappings = mapFields(property.properties); + Object.keys(childMappings).forEach((key) => { + acc[curr + '.' + key] = childMappings[key]; + }); + } else { + acc[curr] = property; + } + return acc; + }, {} as any); + return mappings; +} + +export function builRoutingPath(properties: Record) { + const mappingsProperties = mapFields(properties); + return Object.keys(mappingsProperties).filter( + (mapping) => + mappingsProperties[mapping].type === 'keyword' && + mappingsProperties[mapping].time_series_dimension + ); +} + export async function handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, @@ -42,13 +71,12 @@ export async function handleExperimentalDatastreamFeatureOptIn({ (optIn) => optIn.data_stream === featureMapEntry.data_stream ); - const isOptInChanged = + const isSyntheticSourceOptInChanged = existingOptIn?.features.synthetic_source !== featureMapEntry.features.synthetic_source; - // If the feature opt-in status in unchanged, we don't need to update any component templates - if (!isOptInChanged) { - continue; - } + const isTSDBOptInChanged = existingOptIn?.features.tsdb !== featureMapEntry.features.tsdb; + + if (!isSyntheticSourceOptInChanged && !isTSDBOptInChanged) continue; const componentTemplateName = `${featureMapEntry.data_stream}@package`; const componentTemplateRes = await esClient.cluster.getComponentTemplate({ @@ -57,23 +85,59 @@ export async function handleExperimentalDatastreamFeatureOptIn({ const componentTemplate = componentTemplateRes.component_templates[0].component_template; - const body = { - template: { - ...componentTemplate.template, - mappings: { - ...componentTemplate.template.mappings, - _source: { - mode: featureMapEntry.features.synthetic_source ? 'synthetic' : 'stored', + if (isSyntheticSourceOptInChanged) { + const body = { + template: { + ...componentTemplate.template, + mappings: { + ...componentTemplate.template.mappings, + _source: { + mode: featureMapEntry.features.synthetic_source ? 'synthetic' : 'stored', + }, }, }, - }, - }; + }; - await esClient.cluster.putComponentTemplate({ - name: componentTemplateName, - // @ts-expect-error - TODO: Remove when ES client typings include support for synthetic source - body, - }); + await esClient.cluster.putComponentTemplate({ + name: componentTemplateName, + // @ts-expect-error - TODO: Remove when ES client typings include support for synthetic source + body, + }); + } + + if (isTSDBOptInChanged && featureMapEntry.features.tsdb) { + const mappingsProperties = componentTemplate.template?.mappings?.properties ?? {}; + + // All mapped fields of type keyword and time_series_dimension enabled will be included in the generated routing path + // Temporarily generating routing_path here until fixed in elasticsearch https://github.com/elastic/elasticsearch/issues/91592 + const routingPath = builRoutingPath(mappingsProperties); + + if (routingPath.length === 0) continue; + + const indexTemplateRes = await esClient.indices.getIndexTemplate({ + name: featureMapEntry.data_stream, + }); + const indexTemplate = indexTemplateRes.index_templates[0].index_template; + + const indexTemplateBody = { + ...indexTemplate, + template: { + ...(indexTemplate.template ?? {}), + settings: { + ...(indexTemplate.template?.settings ?? {}), + index: { + mode: 'time_series', + routing_path: routingPath, + }, + }, + }, + }; + + await esClient.indices.putIndexTemplate({ + name: featureMapEntry.data_stream, + body: indexTemplateBody, + }); + } } // Update the installation object to persist the experimental feature map diff --git a/x-pack/plugins/fleet/server/services/setup.test.ts b/x-pack/plugins/fleet/server/services/setup.test.ts index 34336f8167316e..8837ae3522ac1a 100644 --- a/x-pack/plugins/fleet/server/services/setup.test.ts +++ b/x-pack/plugins/fleet/server/services/setup.test.ts @@ -60,6 +60,7 @@ describe('setupFleet', () => { (upgradeManagedPackagePolicies as jest.Mock).mockResolvedValue([]); soClient.find.mockResolvedValue({ saved_objects: [] } as any); + soClient.bulkGet.mockResolvedValue({ saved_objects: [] } as any); }); afterEach(async () => { diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index 3cb2dc030cf758..e43efb44adb5fc 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -12,7 +12,7 @@ import pMap from 'p-map'; import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; -import { AUTO_UPDATE_PACKAGES } from '../../common/constants'; +import { AUTO_UPDATE_PACKAGES, FILE_STORAGE_INTEGRATION_NAMES } from '../../common/constants'; import type { PreconfigurationError } from '../../common/constants'; import type { DefaultPackagesInstallationError, @@ -36,7 +36,10 @@ import { ensureDefaultEnrollmentAPIKeyForAgentPolicy } from './api_keys'; import { getRegistryUrl, settingsService } from '.'; import { awaitIfPending } from './setup_utils'; import { ensureFleetFinalPipelineIsInstalled } from './epm/elasticsearch/ingest_pipeline/install'; -import { ensureDefaultComponentTemplates } from './epm/elasticsearch/template/install'; +import { + ensureDefaultComponentTemplates, + ensureFileUploadWriteIndices, +} from './epm/elasticsearch/template/install'; import { getInstallations, reinstallPackageForInstallation } from './epm/packages'; import { isPackageInstalled } from './epm/packages/install'; import type { UpgradeManagedPackagePoliciesResult } from './managed_package_policies'; @@ -49,6 +52,7 @@ import { ensurePreconfiguredFleetServerHosts, getPreconfiguredFleetServerHostFromConfig, } from './preconfiguration/fleet_server_host'; +import { getInstallationsByName } from './epm/packages/get'; export interface SetupStatus { isInitialized: boolean; @@ -108,6 +112,7 @@ async function createSetupSideEffects( await ensureFleetGlobalEsAssets(soClient, esClient); } + await ensureFleetFileUploadIndices(soClient, esClient); // Ensure that required packages are always installed even if they're left out of the config const preconfiguredPackageNames = new Set(packages.map((pkg) => pkg.name)); @@ -168,6 +173,30 @@ async function createSetupSideEffects( }; } +/** + * Ensure ES assets shared by all Fleet index template are installed + */ +export async function ensureFleetFileUploadIndices( + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient +) { + const { diagnosticFileUploadEnabled } = appContextService.getExperimentalFeatures(); + if (!diagnosticFileUploadEnabled) return; + const logger = appContextService.getLogger(); + const installedFileUploadIntegrations = await getInstallationsByName({ + savedObjectsClient: soClient, + pkgNames: [...FILE_STORAGE_INTEGRATION_NAMES], + }); + + if (!installedFileUploadIntegrations.length) return []; + const integrationNames = installedFileUploadIntegrations.map(({ name }) => name); + logger.debug(`Ensuring file upload write indices for ${integrationNames}`); + return ensureFileUploadWriteIndices({ + esClient, + logger, + integrationNames, + }); +} /** * Ensure ES assets shared by all Fleet index template are installed */ diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts index 82ff572cd1926e..64837501b941bb 100644 --- a/x-pack/plugins/fleet/server/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts @@ -84,6 +84,7 @@ const ExperimentalDataStreamFeatures = schema.arrayOf( data_stream: schema.string(), features: schema.object({ synthetic_source: schema.boolean(), + tsdb: schema.boolean(), }), }) ); @@ -128,7 +129,7 @@ const CreatePackagePolicyProps = { schema.arrayOf( schema.object({ data_stream: schema.string(), - features: schema.object({ synthetic_source: schema.boolean() }), + features: schema.object({ synthetic_source: schema.boolean(), tsdb: schema.boolean() }), }) ) ), 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 bbd0afa495e489..4a251d41d69d9b 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 @@ -21,7 +21,8 @@ export const defaultScaledFloatParameters = { store: false, }; -describe('Mappings editor: scaled float datatype', () => { +// FLAKY: https://github.com/elastic/kibana/issues/145102 +describe.skip('Mappings editor: scaled float datatype', () => { /** * Variable to store the mappings data forwarded to the consumer component */ diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts index 1f360a671aaae4..0148feff83d584 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts @@ -62,6 +62,8 @@ describe('useExceptionLists', () => { }, expect.any(Function), expect.any(Function), + { field: 'created_at', order: 'desc' }, + expect.any(Function), ]); }); }); @@ -102,6 +104,8 @@ describe('useExceptionLists', () => { }, expect.any(Function), expect.any(Function), + { field: 'created_at', order: 'desc' }, + expect.any(Function), ]); }); }); @@ -137,6 +141,7 @@ describe('useExceptionLists', () => { namespaceTypes: 'single,agnostic', pagination: { page: 1, perPage: 20 }, signal: new AbortController().signal, + sort: { field: 'created_at', order: 'desc' }, }); }); }); @@ -175,6 +180,10 @@ describe('useExceptionLists', () => { namespaceTypes: 'single,agnostic', pagination: { page: 1, perPage: 20 }, signal: new AbortController().signal, + sort: { + field: 'created_at', + order: 'desc', + }, }); }); }); diff --git a/x-pack/plugins/lists/server/services/exception_lists/bulk_create_exception_list_items.ts b/x-pack/plugins/lists/server/services/exception_lists/bulk_create_exception_list_items.ts new file mode 100644 index 00000000000000..de21a883e9ab32 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/bulk_create_exception_list_items.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsClientContract } from '@kbn/core/server'; +import uuid from 'uuid'; +import type { + CreateExceptionListItemSchema, + ExceptionListItemSchema, +} from '@kbn/securitysolution-io-ts-list-types'; +import { SavedObjectType, getSavedObjectType } from '@kbn/securitysolution-list-utils'; + +import { ExceptionListSoSchema } from '../../schemas/saved_objects'; + +import { transformSavedObjectToExceptionListItem } from './utils'; + +interface BulkCreateExceptionListItemsOptions { + items: CreateExceptionListItemSchema[]; + savedObjectsClient: SavedObjectsClientContract; + user: string; + tieBreaker?: string; +} + +export const bulkCreateExceptionListItems = async ({ + items, + savedObjectsClient, + tieBreaker, + user, +}: BulkCreateExceptionListItemsOptions): Promise => { + const formattedItems = items.map((item) => { + const savedObjectType = getSavedObjectType({ namespaceType: item.namespace_type ?? 'single' }); + const dateNow = new Date().toISOString(); + + return { + attributes: { + comments: [], + created_at: dateNow, + created_by: user, + description: item.description, + entries: item.entries, + immutable: false, + item_id: item.item_id, + list_id: item.list_id, + list_type: 'item', + meta: item.meta, + name: item.name, + os_types: item.os_types, + tags: item.tags, + tie_breaker_id: tieBreaker ?? uuid.v4(), + type: item.type, + updated_by: user, + version: undefined, + }, + type: savedObjectType, + } as { attributes: ExceptionListSoSchema; type: SavedObjectType }; + }); + + const { saved_objects: savedObjects } = + await savedObjectsClient.bulkCreate(formattedItems); + + const result = savedObjects.map((so) => + transformSavedObjectToExceptionListItem({ savedObject: so }) + ); + + return result; +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/duplicate_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/duplicate_exception_list.ts new file mode 100644 index 00000000000000..f2d0723590cf57 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/duplicate_exception_list.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsClientContract } from '@kbn/core/server'; +import uuid from 'uuid'; +import { + CreateExceptionListItemSchema, + ExceptionListSchema, + ExceptionListTypeEnum, + FoundExceptionListItemSchema, + ListId, + NamespaceType, +} from '@kbn/securitysolution-io-ts-list-types'; + +import { findExceptionListsItemPointInTimeFinder } from './find_exception_list_items_point_in_time_finder'; +import { bulkCreateExceptionListItems } from './bulk_create_exception_list_items'; +import { getExceptionList } from './get_exception_list'; +import { createExceptionList } from './create_exception_list'; + +const LISTS_ABLE_TO_DUPLICATE = [ + ExceptionListTypeEnum.DETECTION.toString(), + ExceptionListTypeEnum.RULE_DEFAULT.toString(), +]; + +interface CreateExceptionListOptions { + listId: ListId; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; + user: string; +} + +export const duplicateExceptionListAndItems = async ({ + listId, + savedObjectsClient, + namespaceType, + user, +}: CreateExceptionListOptions): Promise => { + // Generate a new static listId + const newListId = uuid.v4(); + + // fetch list container + const listToDuplicate = await getExceptionList({ + id: undefined, + listId, + namespaceType, + savedObjectsClient, + }); + + if (listToDuplicate == null) { + throw new Error(`Exception list to duplicat of list_id:${listId} not found.`); + } + + if (!LISTS_ABLE_TO_DUPLICATE.includes(listToDuplicate.type)) { + throw new Error(`Exception list of type:${listToDuplicate.type} cannot be duplicated.`); + } + + const newlyCreatedList = await createExceptionList({ + description: listToDuplicate.description, + immutable: listToDuplicate.immutable, + listId: newListId, + meta: listToDuplicate.meta, + name: listToDuplicate.name, + namespaceType: listToDuplicate.namespace_type, + savedObjectsClient, + tags: listToDuplicate.tags, + type: listToDuplicate.type, + user, + version: 1, + }); + + // fetch associated items + let itemsToBeDuplicated: CreateExceptionListItemSchema[] = []; + const executeFunctionOnStream = (response: FoundExceptionListItemSchema): void => { + const transformedItems = response.data.map((item) => { + // Generate a new static listId + const newItemId = uuid.v4(); + + return { + comments: [], + description: item.description, + entries: item.entries, + item_id: newItemId, + list_id: newlyCreatedList.list_id, + meta: item.meta, + name: item.name, + namespace_type: item.namespace_type, + os_types: item.os_types, + tags: item.tags, + type: item.type, + }; + }); + itemsToBeDuplicated = [...itemsToBeDuplicated, ...transformedItems]; + }; + await findExceptionListsItemPointInTimeFinder({ + executeFunctionOnStream, + filter: [], + listId: [listId], + maxSize: 10000, + namespaceType: [namespaceType], + perPage: undefined, + savedObjectsClient, + sortField: undefined, + sortOrder: undefined, + }); + + await bulkCreateExceptionListItems({ + items: itemsToBeDuplicated, + savedObjectsClient, + user, + }); + + return newlyCreatedList; +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts index ecdaa70d7869bb..562ebffc9f00c1 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts @@ -39,6 +39,7 @@ import type { DeleteExceptionListItemByIdOptions, DeleteExceptionListItemOptions, DeleteExceptionListOptions, + DuplicateExceptionListOptions, ExportExceptionListAndItemsOptions, FindEndpointListItemOptions, FindExceptionListItemOptions, @@ -95,6 +96,7 @@ import { findValueListExceptionListItems } from './find_value_list_exception_lis import { findExceptionListsItemPointInTimeFinder } from './find_exception_list_items_point_in_time_finder'; import { findValueListExceptionListItemsPointInTimeFinder } from './find_value_list_exception_list_items_point_in_time_finder'; import { findExceptionListItemPointInTimeFinder } from './find_exception_list_item_point_in_time_finder'; +import { duplicateExceptionListAndItems } from './duplicate_exception_list'; /** * Class for use for exceptions that are with trusted applications or @@ -311,6 +313,25 @@ export class ExceptionListClient { }); }; + /** + * Create the Trusted Apps Agnostic list if it does not yet exist (`null` is returned if it does exist) + * @param options.listId the "list_id" of the exception list + * @param options.namespaceType saved object namespace (single | agnostic) + * @returns The exception list schema or null if it does not exist + */ + public duplicateExceptionListAndItems = async ({ + listId, + namespaceType, + }: DuplicateExceptionListOptions): Promise => { + const { savedObjectsClient, user } = this; + return duplicateExceptionListAndItems({ + listId, + namespaceType, + savedObjectsClient, + user, + }); + }; + /** * This is the same as "updateExceptionListItem" except it applies specifically to the endpoint list and will * auto-call the "createEndpointList" for you so that you have the best chance of the endpoint diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts index 35a28c01160355..6b87945710a378 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts @@ -287,6 +287,17 @@ export interface CreateEndpointListItemOptions { type: ExceptionListItemType; } +/** + * ExceptionListClient.duplicateExceptionListAndItems + * {@link ExceptionListClient.duplicateExceptionListAndItems} + */ +export interface DuplicateExceptionListOptions { + /** The single list id to do the search against */ + listId: ListId; + /** saved object namespace (single | agnostic) */ + namespaceType: NamespaceType; +} + /** * ExceptionListClient.updateExceptionListItem * {@link ExceptionListClient.updateExceptionListItem} diff --git a/x-pack/plugins/ml/common/constants/locator.ts b/x-pack/plugins/ml/common/constants/locator.ts index f4aa35675cfd1f..36cdedee82c408 100644 --- a/x-pack/plugins/ml/common/constants/locator.ts +++ b/x-pack/plugins/ml/common/constants/locator.ts @@ -62,6 +62,8 @@ export const ML_PAGES = { AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT: 'aiops/explain_log_rate_spikes_index_select', AIOPS_LOG_CATEGORIZATION: 'aiops/log_categorization', AIOPS_LOG_CATEGORIZATION_INDEX_SELECT: 'aiops/log_categorization_index_select', + AIOPS_CHANGE_POINT_DETECTION: 'aiops/change_point_detection', + AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT: 'aiops/change_point_detection_index_select', } as const; export type MlPages = typeof ML_PAGES[keyof typeof ML_PAGES]; diff --git a/x-pack/plugins/ml/common/types/locator.ts b/x-pack/plugins/ml/common/types/locator.ts index 973b2bb7335a39..98e2ecb0ede5dc 100644 --- a/x-pack/plugins/ml/common/types/locator.ts +++ b/x-pack/plugins/ml/common/types/locator.ts @@ -65,7 +65,9 @@ export type MlGenericUrlState = MLPageState< | typeof ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES | typeof ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT | typeof ML_PAGES.AIOPS_LOG_CATEGORIZATION - | typeof ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT, + | typeof ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT + | typeof ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT + | typeof ML_PAGES.AIOPS_CHANGE_POINT_DETECTION, MlGenericUrlPageState | undefined >; diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json index 8104d9ef3d51dd..c7191118e25999 100644 --- a/x-pack/plugins/ml/kibana.json +++ b/x-pack/plugins/ml/kibana.json @@ -17,6 +17,7 @@ "embeddable", "features", "fieldFormats", + "lens", "licensing", "share", "taskManager", @@ -28,7 +29,6 @@ "alerting", "dashboard", "home", - "lens", "licenseManagement", "management", "maps", diff --git a/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx b/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx new file mode 100644 index 00000000000000..3ee53940add2b5 --- /dev/null +++ b/x-pack/plugins/ml/public/application/aiops/change_point_detection.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import { pick } from 'lodash'; + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { ChangePointDetection } from '@kbn/aiops-plugin/public'; + +import { useMlContext } from '../contexts/ml'; +import { useMlKibana } from '../contexts/kibana'; +import { HelpMenu } from '../components/help_menu'; +import { TechnicalPreviewBadge } from '../components/technical_preview_badge'; + +import { MlPageHeader } from '../components/page_header'; + +export const ChangePointDetectionPage: FC = () => { + const { services } = useMlKibana(); + + const context = useMlContext(); + const dataView = context.currentDataView; + const savedSearch = context.currentSavedSearch; + + return ( + <> + + + + + + + + + + + {dataView ? ( + + ) : null} + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/aiops/explain_log_rate_spikes.tsx b/x-pack/plugins/ml/public/application/aiops/explain_log_rate_spikes.tsx index 9cfa17fe719416..c7abf7385c3b06 100644 --- a/x-pack/plugins/ml/public/application/aiops/explain_log_rate_spikes.tsx +++ b/x-pack/plugins/ml/public/application/aiops/explain_log_rate_spikes.tsx @@ -58,6 +58,7 @@ export const ExplainLogRateSpikesPage: FC = () => { 'uiSettings', 'unifiedSearch', 'theme', + 'lens', ])} /> )} diff --git a/x-pack/plugins/ml/public/application/aiops/index.ts b/x-pack/plugins/ml/public/application/aiops/index.ts index fa47ae09822e2a..48cb2e9f8cd36f 100644 --- a/x-pack/plugins/ml/public/application/aiops/index.ts +++ b/x-pack/plugins/ml/public/application/aiops/index.ts @@ -6,3 +6,4 @@ */ export { ExplainLogRateSpikesPage } from './explain_log_rate_spikes'; +export { ChangePointDetectionPage } from './change_point_detection'; diff --git a/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx b/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx index 3f70e0c58324d0..a78a1e3815be86 100644 --- a/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx +++ b/x-pack/plugins/ml/public/application/aiops/log_categorization.tsx @@ -58,6 +58,7 @@ export const LogCategorizationPage: FC = () => { 'uiSettings', 'unifiedSearch', 'theme', + 'lens', ])} /> )} diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index 1ef553eae92f7a..12bd498c98fa91 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -93,6 +93,7 @@ const App: FC = ({ coreStart, deps, appMountParams }) => { cases: deps.cases, unifiedSearch: deps.unifiedSearch, licensing: deps.licensing, + lens: deps.lens, ...coreStart, }; diff --git a/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx b/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx index 6959f5cf17a53e..2d755d3cb1d54f 100644 --- a/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx +++ b/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { EuiSideNavItemType } from '@elastic/eui'; import React, { ReactNode, useCallback, useMemo } from 'react'; -import { AIOPS_ENABLED } from '@kbn/aiops-plugin/common'; +import { AIOPS_ENABLED, CHANGE_POINT_DETECTION_ENABLED } from '@kbn/aiops-plugin/common'; import { NotificationsIndicator } from './notifications_indicator'; import type { MlLocatorParams } from '../../../../common/types/locator'; import { useUrlState } from '../../util/url_state'; @@ -28,6 +28,8 @@ export interface Tab { onClick?: () => Promise; /** Indicates if item should be marked as active with nested routes */ highlightNestedRoutes?: boolean; + /** List of route IDs related to the side nav entry */ + relatedRouteIds?: string[]; } export function useSideNavItems(activeRoute: MlRoute | undefined) { @@ -252,6 +254,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { }), disabled: disableLinks, testSubj: 'mlMainTab explainLogRateSpikes', + relatedRouteIds: ['explain_log_rate_spikes'], }, { id: 'logCategorization', @@ -261,7 +264,22 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { }), disabled: disableLinks, testSubj: 'mlMainTab logCategorization', + relatedRouteIds: ['log_categorization'], }, + ...(CHANGE_POINT_DETECTION_ENABLED + ? [ + { + id: 'changePointDetection', + pathId: ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT, + name: i18n.translate('xpack.ml.navMenu.changePointDetectionLinkText', { + defaultMessage: 'Change Point Detection', + }), + disabled: disableLinks, + testSubj: 'mlMainTab changePointDetection', + relatedRouteIds: ['change_point_detection'], + }, + ] + : []), ], }); } @@ -271,13 +289,24 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { const getTabItem: (tab: Tab) => EuiSideNavItemType = useCallback( (tab: Tab) => { - const { id, disabled, items, onClick, pathId, name, testSubj, highlightNestedRoutes } = tab; + const { + id, + disabled, + items, + onClick, + pathId, + name, + testSubj, + highlightNestedRoutes, + relatedRouteIds, + } = tab; const onClickCallback = onClick ?? (pathId ? redirectToTab.bind(null, pathId) : undefined); const isSelected = `/${pathId}` === activeRoute?.path || - (!!highlightNestedRoutes && activeRoute?.path.includes(`${pathId}/`)); + (!!highlightNestedRoutes && activeRoute?.path.includes(`${pathId}/`)) || + (Array.isArray(relatedRouteIds) && relatedRouteIds.includes(activeRoute?.id!)); return { id, @@ -290,7 +319,7 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) { forceOpen: true, }; }, - [activeRoute?.path, redirectToTab] + [activeRoute, redirectToTab] ); return useMemo(() => tabsDefinition.map(getTabItem), [tabsDefinition, getTabItem]); diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts index 5b9e2f7d2ab27a..d88d9abb24a87c 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts @@ -24,6 +24,7 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { CasesUiStart } from '@kbn/cases-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { LensPublicStart } from '@kbn/lens-plugin/public'; import type { MlServicesContext } from '../../app'; interface StartPlugins { @@ -45,6 +46,7 @@ interface StartPlugins { unifiedSearch: UnifiedSearchPublicPluginStart; core: CoreStart; appName: string; + lens: LensPublicStart; } export type StartServices = CoreStart & StartPlugins & { diff --git a/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts b/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts index 9ae337cfcd7f09..dc21715d963a80 100644 --- a/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts +++ b/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts @@ -71,6 +71,13 @@ export const AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.fr href: '/aiops/log_categorization_index_select', }); +export const AIOPS_BREADCRUMB_CHANGE_POINT_DETECTION: ChromeBreadcrumb = Object.freeze({ + text: i18n.translate('xpack.ml.aiopsBreadcrumbLabel', { + defaultMessage: 'AIOps Labs', + }), + href: '/aiops/change_point_detection_index_select', +}); + export const EXPLAIN_LOG_RATE_SPIKES: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.aiops.explainLogRateSpikesBreadcrumbLabel', { defaultMessage: 'Explain Log Rate Spikes', @@ -85,6 +92,13 @@ export const LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.freeze({ href: '/aiops/log_categorization_index_select', }); +export const CHANGE_POINT_DETECTION: ChromeBreadcrumb = Object.freeze({ + text: i18n.translate('xpack.ml.aiops.changePointDetectionBreadcrumbLabel', { + defaultMessage: 'Change Point Detection', + }), + href: '/aiops/change_point_detection_index_select', +}); + export const CREATE_JOB_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ text: i18n.translate('xpack.ml.createJobsBreadcrumbLabel', { defaultMessage: 'Create job', @@ -115,8 +129,10 @@ const breadcrumbs = { DATA_VISUALIZER_BREADCRUMB, AIOPS_BREADCRUMB_EXPLAIN_LOG_RATE_SPIKES, AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS, + AIOPS_BREADCRUMB_CHANGE_POINT_DETECTION, EXPLAIN_LOG_RATE_SPIKES, LOG_PATTERN_ANALYSIS, + CHANGE_POINT_DETECTION, CREATE_JOB_BREADCRUMB, CALENDAR_MANAGEMENT_BREADCRUMB, FILTER_LISTS_BREADCRUMB, diff --git a/x-pack/plugins/ml/public/application/routing/routes/aiops/change_point_detection.tsx b/x-pack/plugins/ml/public/application/routing/routes/aiops/change_point_detection.tsx new file mode 100644 index 00000000000000..47be592377825c --- /dev/null +++ b/x-pack/plugins/ml/public/application/routing/routes/aiops/change_point_detection.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CHANGE_POINT_DETECTION_ENABLED } from '@kbn/aiops-plugin/common'; +import { i18n } from '@kbn/i18n'; +import React, { FC } from 'react'; +import { parse } from 'query-string'; +import { NavigateToPath } from '../../../contexts/kibana'; +import { MlRoute } from '../..'; +import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; +import { PageLoader, PageProps } from '../../router'; +import { useResolver } from '../../use_resolver'; +import { checkBasicLicense } from '../../../license'; +import { cacheDataViewsContract } from '../../../util/index_utils'; +import { ChangePointDetectionPage as Page } from '../../../aiops'; + +export const changePointDetectionRouteFactory = ( + navigateToPath: NavigateToPath, + basePath: string +): MlRoute => ({ + id: 'change_point_detection', + path: '/aiops/change_point_detection', + title: i18n.translate('xpack.ml.aiops.changePointDetection.docTitle', { + defaultMessage: 'Change point detection', + }), + render: (props, deps) => , + breadcrumbs: [ + getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), + getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_CHANGE_POINT_DETECTION', navigateToPath, basePath), + { + text: i18n.translate('xpack.ml.aiopsBreadcrumbs.changePointDetectionLabel', { + defaultMessage: 'Change point detection', + }), + }, + ], + disabled: !CHANGE_POINT_DETECTION_ENABLED, +}); + +const PageWrapper: FC = ({ location, deps }) => { + const { index, savedSearchId }: Record = parse(location.search, { sort: false }); + const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, { + checkBasicLicense, + cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract), + }); + + return ( + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/routing/routes/aiops/index.ts b/x-pack/plugins/ml/public/application/routing/routes/aiops/index.ts index 5b55d41e887bc0..92c96f48443266 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/aiops/index.ts +++ b/x-pack/plugins/ml/public/application/routing/routes/aiops/index.ts @@ -7,3 +7,4 @@ export * from './explain_log_rate_spikes'; export * from './log_categorization'; +export * from './change_point_detection'; diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx index 9323d86d32b991..b7513db067e2f2 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx @@ -55,7 +55,7 @@ const getExplainLogRateSpikesBreadcrumbs = (navigateToPath: NavigateToPath, base getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_EXPLAIN_LOG_RATE_SPIKES', navigateToPath, basePath), getBreadcrumbWithUrlForApp('EXPLAIN_LOG_RATE_SPIKES', navigateToPath, basePath), { - text: i18n.translate('xpack.ml.aiopsBreadcrumbs.selectDateViewLabel', { + text: i18n.translate('xpack.ml.aiopsBreadcrumbs.selectDataViewLabel', { defaultMessage: 'Select Data View', }), }, @@ -66,7 +66,18 @@ const getLogCategorizationBreadcrumbs = (navigateToPath: NavigateToPath, basePat getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS', navigateToPath, basePath), getBreadcrumbWithUrlForApp('LOG_PATTERN_ANALYSIS', navigateToPath, basePath), { - text: i18n.translate('xpack.ml.aiopsBreadcrumbs.selectDateViewLabel', { + text: i18n.translate('xpack.ml.aiopsBreadcrumbs.selectDataViewLabel', { + defaultMessage: 'Select Data View', + }), + }, +]; + +const getChangePointDetectionBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [ + getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath), + getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_CHANGE_POINT_DETECTION', navigateToPath, basePath), + getBreadcrumbWithUrlForApp('CHANGE_POINT_DETECTION', navigateToPath, basePath), + { + text: i18n.translate('xpack.ml.aiopsBreadcrumbs.selectDataViewLabel', { defaultMessage: 'Select Data View', }), }, @@ -148,6 +159,26 @@ export const logCategorizationIndexOrSearchRouteFactory = ( breadcrumbs: getLogCategorizationBreadcrumbs(navigateToPath, basePath), }); +export const changePointDetectionIndexOrSearchRouteFactory = ( + navigateToPath: NavigateToPath, + basePath: string +): MlRoute => ({ + id: 'data_view_change_point_detection', + path: '/aiops/change_point_detection_index_select', + title: i18n.translate('xpack.ml.selectDataViewLabel', { + defaultMessage: 'Select Data View', + }), + render: (props, deps) => ( + + ), + breadcrumbs: getChangePointDetectionBreadcrumbs(navigateToPath, basePath), +}); + const PageWrapper: FC = ({ nextStepPath, deps, mode }) => { const { services: { diff --git a/x-pack/plugins/ml/public/locator/ml_locator.ts b/x-pack/plugins/ml/public/locator/ml_locator.ts index a742700cceaec2..6860720d451427 100644 --- a/x-pack/plugins/ml/public/locator/ml_locator.ts +++ b/x-pack/plugins/ml/public/locator/ml_locator.ts @@ -90,6 +90,8 @@ export class MlLocatorDefinition implements LocatorDefinition { case ML_PAGES.AIOPS_EXPLAIN_LOG_RATE_SPIKES_INDEX_SELECT: case ML_PAGES.AIOPS_LOG_CATEGORIZATION: case ML_PAGES.AIOPS_LOG_CATEGORIZATION_INDEX_SELECT: + case ML_PAGES.AIOPS_CHANGE_POINT_DETECTION: + case ML_PAGES.AIOPS_CHANGE_POINT_DETECTION_INDEX_SELECT: case ML_PAGES.OVERVIEW: case ML_PAGES.SETTINGS: case ML_PAGES.FILTER_LISTS_MANAGE: diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index 6bb6da5b090694..e34afb42cde9dc 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -656,20 +656,7 @@ export class DataVisualizer { }, }; - // If cardinality >= SAMPLE_TOP_TERMS_THRESHOLD, run the top terms aggregation - // in a sampler aggregation, even if no sampling has been specified (samplerShardSize < 1). - if (samplerShardSize < 1 && field.cardinality >= SAMPLER_TOP_TERMS_THRESHOLD) { - aggs[`${safeFieldName}_top`] = { - sampler: { - shard_size: SAMPLER_TOP_TERMS_SHARD_SIZE, - }, - aggs: { - top, - }, - }; - } else { - aggs[`${safeFieldName}_top`] = top; - } + aggs[`${safeFieldName}_top`] = top; }); const searchBody = { @@ -782,20 +769,7 @@ export class DataVisualizer { }, }; - // If cardinality >= SAMPLE_TOP_TERMS_THRESHOLD, run the top terms aggregation - // in a sampler aggregation, even if no sampling has been specified (samplerShardSize < 1). - if (samplerShardSize < 1 && field.cardinality >= SAMPLER_TOP_TERMS_THRESHOLD) { - aggs[`${safeFieldName}_top`] = { - sampler: { - shard_size: SAMPLER_TOP_TERMS_SHARD_SIZE, - }, - aggs: { - top, - }, - }; - } else { - aggs[`${safeFieldName}_top`] = top; - } + aggs[`${safeFieldName}_top`] = top; }); const searchBody = { diff --git a/x-pack/plugins/monitoring/common/types/es.ts b/x-pack/plugins/monitoring/common/types/es.ts index 977753de42d97a..013d504fa8a732 100644 --- a/x-pack/plugins/monitoring/common/types/es.ts +++ b/x-pack/plugins/monitoring/common/types/es.ts @@ -17,6 +17,7 @@ export interface ElasticsearchResponse { } export interface ElasticsearchResponseHit { + _id: string; _index: string; _source: ElasticsearchSource; inner_hits?: { diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.test.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.test.js index 9c778553ca1a13..a89934d3867422 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.test.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.test.js @@ -37,8 +37,11 @@ describe('get_shard_allocation', () => { describe('handleResponse', () => { it('deduplicates shards', () => { const nextTimestamp = '2018-07-06T00:00:01.259Z'; - const hits = shards.map((shard) => { + const hits = shards.map((shard, index) => { return { + _id: `STATE_UUID:${shard.node}:${shard.index}:s${shard.shard}:${ + shard.primary ? 'p' : `r${index}` + }`, _source: { ...exampleShardSource, shard, diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts index ab4fc1286d5676..dd61ad4e1e59d6 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_allocation.ts @@ -36,9 +36,8 @@ export function handleResponse(response: ElasticsearchResponse) { mbShard?.shard?.relocating_node?.id ?? legacyShard?.relocating_node ?? null; const node = mbShard?.node?.id ?? legacyShard?.node; // note: if the request is for a node, then it's enough to deduplicate without primary, but for indices it displays both - const shardId = `${index}-${shardNumber}-${primary}-${relocatingNode}-${node}`; - if (!uniqueShards.has(shardId)) { + if (!uniqueShards.has(hit._id)) { // @ts-ignore shards.push({ index, @@ -48,7 +47,7 @@ export function handleResponse(response: ElasticsearchResponse) { shard: shardNumber, state: legacyShard?.state ?? mbShard?.shard?.state, }); - uniqueShards.add(shardId); + uniqueShards.add(hit._id); } } diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.test.ts b/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.test.ts index a43eb9a7cd09fe..50410f4ef0ea5e 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.test.ts +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_node_info.test.ts @@ -33,6 +33,7 @@ jest.mock('../../static_globals', () => ({ // deletes, adds, or updates the properties based on a default object function createResponseObjHit(params?: HitParams[]): ElasticsearchResponseHit { const defaultResponseObj: ElasticsearchResponseHit = { + _id: '123123a', _index: 'index', _source: { cluster_uuid: '123', diff --git a/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.test.tsx b/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.test.tsx new file mode 100644 index 00000000000000..bdf565a29cbdb2 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.test.tsx @@ -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 { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks'; +import React from 'react'; +import { act, waitFor } from '@testing-library/react'; +import { AlertSearchBarProps } from './types'; +import { ObservabilityAlertSearchBar } from './alert_search_bar'; +import { observabilityAlertFeatureIds } from '../../../config'; +import { useKibana } from '../../../utils/kibana_react'; +import { kibanaStartMock } from '../../../utils/kibana_react.mock'; +import { render } from '../../../utils/test_helper'; + +const useKibanaMock = useKibana as jest.Mock; +const getAlertsSearchBarMock = jest.fn(); +const ALERT_SEARCH_BAR_DATA_TEST_SUBJ = 'alerts-search-bar'; +const ACTIVE_BUTTON_DATA_TEST_SUBJ = 'alert-status-filter-active-button'; + +jest.mock('../../../utils/kibana_react'); + +const mockKibana = () => { + useKibanaMock.mockReturnValue({ + services: { + ...kibanaStartMock.startContract().services, + triggersActionsUi: { + ...triggersActionsUiMock.createStart(), + getAlertsSearchBar: getAlertsSearchBarMock.mockReturnValue( +
+ ), + }, + }, + }); +}; + +describe('ObservabilityAlertSearchBar', () => { + const renderComponent = (props: Partial = {}) => { + const alertSearchBarProps: AlertSearchBarProps = { + appName: 'testAppName', + rangeFrom: 'now-15m', + setRangeFrom: jest.fn(), + rangeTo: 'now', + setRangeTo: jest.fn(), + kuery: '', + setKuery: jest.fn(), + status: 'active', + setStatus: jest.fn(), + setEsQuery: jest.fn(), + ...props, + }; + return render(); + }; + + beforeAll(() => { + mockKibana(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render alert search bar', async () => { + const observabilitySearchBar = renderComponent(); + + await waitFor(() => + expect(observabilitySearchBar.queryByTestId(ALERT_SEARCH_BAR_DATA_TEST_SUBJ)).toBeTruthy() + ); + }); + + it('should call alert search bar with correct props', () => { + act(() => { + renderComponent(); + }); + + expect(getAlertsSearchBarMock).toHaveBeenCalledWith( + expect.objectContaining({ + appName: 'testAppName', + featureIds: observabilityAlertFeatureIds, + rangeFrom: 'now-15m', + rangeTo: 'now', + query: '', + }), + {} + ); + }); + + it('should filter active alerts', async () => { + const mockedSetEsQuery = jest.fn(); + const mockedFrom = '2022-11-15T09:38:13.604Z'; + const mockedTo = '2022-11-15T09:53:13.604Z'; + const { getByTestId } = renderComponent({ + setEsQuery: mockedSetEsQuery, + rangeFrom: mockedFrom, + rangeTo: mockedTo, + }); + + await act(async () => { + const activeButton = getByTestId(ACTIVE_BUTTON_DATA_TEST_SUBJ); + activeButton.click(); + }); + + expect(mockedSetEsQuery).toHaveBeenCalledWith({ + bool: { + filter: [ + { + bool: { + minimum_should_match: 1, + should: [{ match_phrase: { 'kibana.alert.status': 'active' } }], + }, + }, + { + range: { + '@timestamp': expect.objectContaining({ + format: 'strict_date_optional_time', + gte: mockedFrom, + lte: mockedTo, + }), + }, + }, + ], + must: [], + must_not: [], + should: [], + }, + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.tsx b/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.tsx index 258cc99057730f..d76ebbfd4b4526 100644 --- a/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.tsx +++ b/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar.tsx @@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { Query } from '@kbn/es-query'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useKibana } from '../../../utils/kibana_react'; import { observabilityAlertFeatureIds } from '../../../config'; import { ObservabilityAppServices } from '../../../application/types'; import { AlertsStatusFilter } from './components'; diff --git a/x-pack/plugins/observability/public/components/shared/alert_search_bar/types.ts b/x-pack/plugins/observability/public/components/shared/alert_search_bar/types.ts index 7c96dac50c37e8..b9e3c706b18a07 100644 --- a/x-pack/plugins/observability/public/components/shared/alert_search_bar/types.ts +++ b/x-pack/plugins/observability/public/components/shared/alert_search_bar/types.ts @@ -21,10 +21,10 @@ interface AlertSearchBarContainerState { } interface AlertSearchBarStateTransitions { - setRangeFrom: (rangeFrom: string) => AlertSearchBarContainerState; - setRangeTo: (rangeTo: string) => AlertSearchBarContainerState; - setKuery: (kuery: string) => AlertSearchBarContainerState; - setStatus: (status: AlertStatus) => AlertSearchBarContainerState; + setRangeFrom: (rangeFrom: string) => void; + setRangeTo: (rangeTo: string) => void; + setKuery: (kuery: string) => void; + setStatus: (status: AlertStatus) => void; } export interface CommonAlertSearchBarProps { diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx index c7fb176bbb65d8..373b57e0e61af3 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/field_value_selection.tsx @@ -9,6 +9,8 @@ import React, { FormEvent, useEffect, useState } from 'react'; import { EuiText, EuiButton, + EuiSwitch, + EuiSpacer, EuiFilterButton, EuiPopover, EuiPopoverFooter, @@ -16,6 +18,7 @@ import { EuiSelectable, EuiSelectableOption, EuiLoadingSpinner, + useEuiTheme, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; @@ -72,14 +75,24 @@ export function FieldValueSelection({ excludedValue, allowExclusions = true, compressed = true, + useLogicalAND, + showLogicalConditionSwitch = false, onChange: onSelectionChange, }: FieldValueSelectionProps) { + const { euiTheme } = useEuiTheme(); + const [options, setOptions] = useState(() => formatOptions(values, selectedValue, excludedValue, showCount) ); const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [isLogicalAND, setIsLogicalAND] = useState(useLogicalAND); + + useEffect(() => { + setIsLogicalAND(useLogicalAND); + }, [useLogicalAND]); + useEffect(() => { setOptions(formatOptions(values, selectedValue, excludedValue, showCount)); }, [values, selectedValue, showCount, excludedValue]); @@ -143,7 +156,13 @@ export function FieldValueSelection({ .filter((opt) => opt?.checked === 'off') .map(({ label: labelN }) => labelN); - return isEqual(selectedValue ?? [], currSelected) && isEqual(excludedValue ?? [], currExcluded); + const hasFilterSelected = (selectedValue ?? []).length > 0 || (excludedValue ?? []).length > 0; + + return ( + isEqual(selectedValue ?? [], currSelected) && + isEqual(excludedValue ?? [], currExcluded) && + !(isLogicalAND !== useLogicalAND && hasFilterSelected) + ); }; return ( @@ -190,6 +209,34 @@ export function FieldValueSelection({ )} + {showLogicalConditionSwitch && ( + <> + +
+ { + setIsLogicalAND(e.target.checked); + }} + /> +
+ + + )} + opt?.checked === 'on'); const excludedValuesN = options.filter((opt) => opt?.checked === 'off'); - onSelectionChange(map(selectedValuesN, 'label'), map(excludedValuesN, 'label')); + if (showLogicalConditionSwitch) { + onSelectionChange( + map(selectedValuesN, 'label'), + map(excludedValuesN, 'label'), + isLogicalAND + ); + } else { + onSelectionChange( + map(selectedValuesN, 'label'), + map(excludedValuesN, 'label') + ); + } + setIsPopoverOpen(false); setForceOpen?.(false); }} diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx index 2cd8487c570483..ea414260a43803 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/index.tsx @@ -36,6 +36,8 @@ export function FieldValueSuggestions({ inspector, asCombobox = true, keepHistory = true, + showLogicalConditionSwitch, + useLogicalAND, onChange: onSelectionChange, }: FieldValueSuggestionsProps) { const [query, setQuery] = useState(''); @@ -77,6 +79,8 @@ export function FieldValueSuggestions({ allowExclusions={allowExclusions} allowAllValuesSelection={singleSelection ? false : allowAllValuesSelection} required={required} + showLogicalConditionSwitch={showLogicalConditionSwitch} + useLogicalAND={useLogicalAND} /> ); } diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts index 59b352c27e5d83..51e89570bc241e 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/types.ts @@ -30,13 +30,15 @@ interface CommonProps { cardinalityField?: string; required?: boolean; keepHistory?: boolean; + showLogicalConditionSwitch?: boolean; + useLogicalAND?: boolean; + onChange: (val?: string[], excludedValue?: string[], isLogicalAND?: boolean) => void; } export type FieldValueSuggestionsProps = CommonProps & { dataViewTitle?: string; sourceField: string; asCombobox?: boolean; - onChange: (val?: string[], excludedValue?: string[]) => void; filters: ESFilter[]; time?: { from: string; to: string }; inspector?: IInspectorInfo; @@ -44,7 +46,6 @@ export type FieldValueSuggestionsProps = CommonProps & { export type FieldValueSelectionProps = CommonProps & { loading?: boolean; - onChange: (val?: string[], excludedValue?: string[]) => void; values?: ListItem[]; query?: string; setQuery: Dispatch>; diff --git a/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts b/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts index fe79b0650d5211..31aa38cb2fd498 100644 --- a/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts +++ b/x-pack/plugins/observability/public/observability_public_plugins_start.mock.ts @@ -27,6 +27,7 @@ const triggersActionsUiStartMock = { createStart() { return { getAddAlertFlyout: jest.fn(), + getAlertsSearchBar: jest.fn(), getRuleStatusDropdown: jest.fn(), getRuleTagBadge: jest.fn(), getRuleStatusFilter: jest.fn(), @@ -54,6 +55,11 @@ const data = { dataViews: { create: jest.fn(), }, + query: { + timefilter: { + timefilter: jest.fn(), + }, + }, }; }, }; diff --git a/x-pack/plugins/profiling/common/elasticsearch.ts b/x-pack/plugins/profiling/common/elasticsearch.ts index 935a148c58667a..bc38a46046fd40 100644 --- a/x-pack/plugins/profiling/common/elasticsearch.ts +++ b/x-pack/plugins/profiling/common/elasticsearch.ts @@ -74,7 +74,6 @@ export type ProfilingESEvent = DedotObject<{ }>; export type ProfilingStackTrace = DedotObject<{ - [ProfilingESField.Timestamp]: number; [ProfilingESField.StacktraceFrameIDs]: string; [ProfilingESField.StacktraceFrameTypes]: string; }>; @@ -90,5 +89,4 @@ export type ProfilingStackFrame = DedotObject<{ export type ProfilingExecutable = DedotObject<{ [ProfilingESField.ExecutableBuildID]: string; [ProfilingESField.ExecutableFileName]: string; - [ProfilingESField.Timestamp]: string; }>; diff --git a/x-pack/plugins/profiling/common/index.ts b/x-pack/plugins/profiling/common/index.ts index 01994865abafef..5c0e469585d53a 100644 --- a/x-pack/plugins/profiling/common/index.ts +++ b/x-pack/plugins/profiling/common/index.ts @@ -27,6 +27,8 @@ export function getRoutePaths() { TopNThreads: `${BASE_ROUTE_PATH}/topn/threads`, TopNTraces: `${BASE_ROUTE_PATH}/topn/traces`, Flamechart: `${BASE_ROUTE_PATH}/flamechart`, + CacheExecutables: `${BASE_ROUTE_PATH}/cache/executables`, + CacheStackFrames: `${BASE_ROUTE_PATH}/cache/stackframes`, }; } diff --git a/x-pack/plugins/profiling/server/routes/cache.ts b/x-pack/plugins/profiling/server/routes/cache.ts new file mode 100644 index 00000000000000..21ba32667e55fe --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/cache.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RouteRegisterParameters } from '.'; +import { getRoutePaths } from '../../common'; +import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; +import { clearExecutableCache, clearStackFrameCache } from './stacktrace'; + +export function registerCacheExecutablesRoute({ router, logger }: RouteRegisterParameters) { + const paths = getRoutePaths(); + router.delete( + { + path: paths.CacheExecutables, + validate: {}, + }, + async (context, request, response) => { + try { + logger.info(`clearing executable cache`); + const numDeleted = clearExecutableCache(); + logger.info(`removed ${numDeleted} executables from cache`); + + return response.ok({}); + } catch (error) { + return handleRouteHandlerError({ error, logger, response }); + } + } + ); +} + +export function registerCacheStackFramesRoute({ router, logger }: RouteRegisterParameters) { + const paths = getRoutePaths(); + router.delete( + { + path: paths.CacheStackFrames, + validate: {}, + }, + async (context, request, response) => { + try { + logger.info(`clearing stackframe cache`); + const numDeleted = clearStackFrameCache(); + logger.info(`removed ${numDeleted} stackframes from cache`); + + return response.ok({}); + } catch (error) { + return handleRouteHandlerError({ error, logger, response }); + } + } + ); +} diff --git a/x-pack/plugins/profiling/server/routes/index.ts b/x-pack/plugins/profiling/server/routes/index.ts index b6bd705ba0e074..a3692b213a0afa 100644 --- a/x-pack/plugins/profiling/server/routes/index.ts +++ b/x-pack/plugins/profiling/server/routes/index.ts @@ -12,6 +12,8 @@ import { ProfilingRequestHandlerContext, } from '../types'; +import { registerCacheExecutablesRoute, registerCacheStackFramesRoute } from './cache'; + import { registerFlameChartSearchRoute } from './flamechart'; import { registerTopNFunctionsSearchRoute } from './functions'; @@ -33,6 +35,8 @@ export interface RouteRegisterParameters { } export function registerRoutes(params: RouteRegisterParameters) { + registerCacheExecutablesRoute(params); + registerCacheStackFramesRoute(params); registerFlameChartSearchRoute(params); registerTopNFunctionsSearchRoute(params); registerTraceEventsTopNContainersSearchRoute(params); diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.ts b/x-pack/plugins/profiling/server/routes/stacktrace.ts index 1dc040c3d3f194..558c560f621926 100644 --- a/x-pack/plugins/profiling/server/routes/stacktrace.ts +++ b/x-pack/plugins/profiling/server/routes/stacktrace.ts @@ -278,6 +278,13 @@ const frameLRU = new LRUCache({ maxAge: CACHE_TTL_MILLISECONDS, }); +// clearStackFrameCache clears the entire cache and returns the number of deleted items +export function clearStackFrameCache(): number { + const numDeleted = frameLRU.length; + frameLRU.reset(); + return numDeleted; +} + export async function mgetStackFrames({ logger, client, @@ -350,6 +357,13 @@ const executableLRU = new LRUCache({ maxAge: CACHE_TTL_MILLISECONDS, }); +// clearExecutableCache clears the entire cache and returns the number of deleted items +export function clearExecutableCache(): number { + const numDeleted = executableLRU.length; + executableLRU.reset(); + return numDeleted; +} + export async function mgetExecutables({ logger, client, diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts index 804fa4bcdd4a6e..91c8c51704718b 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts @@ -830,6 +830,50 @@ it('can override ignoring frozen indices', async () => { ); }); +it('adds a warning if export was unable to close the PIT', async () => { + mockEsClient.asCurrentUser.closePointInTime = jest.fn().mockRejectedValueOnce( + new esErrors.ResponseError({ + statusCode: 419, + warnings: [], + meta: { context: 'test' } as any, + }) + ); + + const generateCsv = new CsvGenerator( + createMockJob({ columns: ['date', 'ip', 'message'] }), + mockConfig, + { + es: mockEsClient, + data: mockDataClient, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + mockLogger, + stream + ); + + await expect(generateCsv.generateData()).resolves.toMatchInlineSnapshot(` + Object { + "content_type": "text/csv", + "csv_contains_formulas": false, + "error_code": undefined, + "max_size_reached": false, + "metrics": Object { + "csv": Object { + "rows": 0, + }, + }, + "warnings": Array [ + "Unable to close the Point-In-Time used for search. Check the Kibana server logs.", + ], + } + `); +}); + it('will return partial data if the scroll or search fails', async () => { mockDataClient.search = jest.fn().mockImplementation(() => { throw new esErrors.ResponseError({ diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts index f527956d5c7fab..e5b38430f9fdce 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts @@ -58,7 +58,7 @@ export class CsvGenerator { private async openPointInTime(indexPatternTitle: string, settings: CsvExportSettings) { const { duration } = settings.scroll; let pitId: string | undefined; - this.logger.debug(`Requesting point-in-time for: [${indexPatternTitle}]...`); + this.logger.debug(`Requesting PIT for: [${indexPatternTitle}]...`); try { // NOTE: if ES is overloaded, this request could time out const response = await this.clients.es.asCurrentUser.openPointInTime( @@ -78,10 +78,10 @@ export class CsvGenerator { } if (!pitId) { - throw new Error(`Could not receive a point-in-time ID!`); + throw new Error(`Could not receive a PIT ID!`); } - this.logger.debug(`Opened PIT ID: ${this.truncatePitId(pitId)}`); + this.logger.debug(`Opened PIT ID: ${this.formatPit(pitId)}`); return pitId; } @@ -100,7 +100,7 @@ export class CsvGenerator { const pitId = searchSource.getField('pit')?.id; this.logger.debug( - `Executing search request with PIT ID: [${this.truncatePitId(pitId)}]` + + `Executing search request with PIT ID: [${this.formatPit(pitId)}]` + (searchAfter ? ` search_after: [${searchAfter}]` : '') ); @@ -378,13 +378,13 @@ export class CsvGenerator { const { pit_id: newPitId, ...header } = headerWithPit; const logInfo = { - header: { pit_id: `${this.truncatePitId(newPitId)}`, ...header }, + header: { pit_id: `${this.formatPit(newPitId)}`, ...header }, hitsMeta, }; this.logger.debug(`Results metadata: ${JSON.stringify(logInfo)}`); // use the most recently received id for the next search request - this.logger.debug(`Received PIT ID: [${this.truncatePitId(results.pit_id)}]`); + this.logger.debug(`Received PIT ID: [${this.formatPit(results.pit_id)}]`); pitId = results.pit_id ?? pitId; // Update last sort results for next query. PIT is used, so the sort results @@ -450,14 +450,18 @@ export class CsvGenerator { } else { warnings.push(i18nTexts.unknownError(err?.message ?? err)); } - } finally { - // + } + + try { if (pitId) { - this.logger.debug(`Closing point-in-time`); + this.logger.debug(`Closing PIT ${this.formatPit(pitId)}`); await this.clients.es.asCurrentUser.closePointInTime({ body: { id: pitId } }); } else { this.logger.warn(`No PIT ID to clear!`); } + } catch (err) { + this.logger.error(err); + warnings.push(i18nTexts.csvUnableToClosePit()); } this.logger.info(`Finished generating. Row count: ${this.csvRowCount}.`); @@ -484,7 +488,8 @@ export class CsvGenerator { }; } - private truncatePitId(pitId: string | undefined) { - return pitId?.substring(0, 12) + '...'; + private formatPit(pitId: string | undefined) { + const byteSize = pitId ? Buffer.byteLength(pitId, 'utf-8') : 0; + return pitId?.substring(0, 12) + `[${byteSize} bytes]`; } } diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/i18n_texts.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/i18n_texts.ts index 1c4cfe4639b1a0..9fdacb220dc56a 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/i18n_texts.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/i18n_texts.ts @@ -39,4 +39,9 @@ export const i18nTexts = { 'Encountered an error with the number of CSV rows generated from the search: expected {expected}, received {received}.', values: { expected, received }, }), + csvUnableToClosePit: () => + i18n.translate('xpack.reporting.exportTypes.csv.generateCsv.csvUnableToClosePit', { + defaultMessage: + 'Unable to close the Point-In-Time used for search. Check the Kibana server logs.', + }), }; diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts index e01ab0105a5d5c..e546f339d2b886 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts @@ -196,6 +196,31 @@ it('matches snapshot', () => { "required": true, "type": "keyword", }, + "kibana.alert.suppression.docs_count": Object { + "array": false, + "required": false, + "type": "long", + }, + "kibana.alert.suppression.end": Object { + "array": false, + "required": false, + "type": "date", + }, + "kibana.alert.suppression.start": Object { + "array": false, + "required": false, + "type": "date", + }, + "kibana.alert.suppression.terms.field": Object { + "array": true, + "required": false, + "type": "keyword", + }, + "kibana.alert.suppression.terms.value": Object { + "array": true, + "required": false, + "type": "keyword", + }, "kibana.alert.system_status": Object { "array": false, "required": false, diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts index 82994950dfd04b..aeebe987e20de6 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts @@ -189,6 +189,31 @@ export const technicalRuleFieldMap = { array: false, required: false, }, + [Fields.ALERT_SUPPRESSION_FIELD]: { + type: 'keyword', + array: true, + required: false, + }, + [Fields.ALERT_SUPPRESSION_VALUE]: { + type: 'keyword', + array: true, + required: false, + }, + [Fields.ALERT_SUPPRESSION_START]: { + type: 'date', + array: false, + required: false, + }, + [Fields.ALERT_SUPPRESSION_END]: { + type: 'date', + array: false, + required: false, + }, + [Fields.ALERT_SUPPRESSION_DOCS_COUNT]: { + type: 'long', + array: false, + required: false, + }, } as const; export type TechnicalRuleFieldMap = typeof technicalRuleFieldMap; 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 ceb0dab8a7f70f..f46908902376f0 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 @@ -234,6 +234,7 @@ describe(`feature_privilege_builder`, () => { "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/bulkEnable", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unsnooze", ] `); @@ -334,6 +335,7 @@ describe(`feature_privilege_builder`, () => { "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/bulkEnable", "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", @@ -394,6 +396,7 @@ describe(`feature_privilege_builder`, () => { "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/bulkEnable", "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", @@ -503,6 +506,7 @@ describe(`feature_privilege_builder`, () => { "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/bulkEnable", "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 19dcd2d3a38cbc..7ea90990a76e50 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 @@ -43,6 +43,7 @@ const writeOperations: Record = { 'snooze', 'bulkEdit', 'bulkDelete', + 'bulkEnable', 'unsnooze', ], alert: ['update'], diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts index 99f5413e6688b9..3cee4c3dbe3848 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts @@ -133,6 +133,7 @@ describe('Perform bulk action request schema', () => { const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkActionType.duplicate, + [BulkActionType.duplicate]: { include_exceptions: false }, }; const message = retrieveValidationMessage(payload); expect(getPaths(left(message.errors))).toEqual([]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts index c09a2c27ea576d..d595dc88441cc2 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts @@ -131,6 +131,14 @@ export const BulkActionEditPayload = t.union([ BulkActionEditPayloadSchedule, ]); +const bulkActionDuplicatePayload = t.exact( + t.type({ + include_exceptions: t.boolean, + }) +); + +export type BulkActionDuplicatePayload = t.TypeOf; + /** * actions that modify rules attributes */ @@ -164,12 +172,23 @@ export const PerformBulkActionRequestBody = t.intersection([ action: t.union([ t.literal(BulkActionType.delete), t.literal(BulkActionType.disable), - t.literal(BulkActionType.duplicate), t.literal(BulkActionType.enable), t.literal(BulkActionType.export), ]), }) ), + t.intersection([ + t.exact( + t.type({ + action: t.literal(BulkActionType.duplicate), + }) + ), + t.exact( + t.partial({ + [BulkActionType.duplicate]: bulkActionDuplicatePayload, + }) + ), + ]), t.exact( t.type({ action: t.literal(BulkActionType.edit), diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/constants.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/constants.ts new file mode 100644 index 00000000000000..710c0b55a86f97 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/constants.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 enum DuplicateOptions { + withExceptions = 'withExceptions', + withoutExceptions = 'withoutExceptions', +} 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 index cf1266b1b9a71e..b63c27f71f79a6 100644 --- 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 @@ -14,6 +14,7 @@ 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/query_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/model/rule_response_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_response_schema.mock.ts index 0a99da6b4f6f32..86018ff1a7b880 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_response_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_response_schema.mock.ts @@ -75,6 +75,7 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): QueryRule filters: undefined, saved_id: undefined, response_actions: undefined, + alert_suppression: undefined, }); export const getSavedQuerySchemaMock = (anchorDate: string = ANCHOR_DATE): SavedQueryRule => ({ @@ -87,6 +88,7 @@ export const getSavedQuerySchemaMock = (anchorDate: string = ANCHOR_DATE): Saved data_view_id: undefined, filters: undefined, response_actions: undefined, + alert_suppression: undefined, }); export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): MachineLearningRule => { 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 index 9985ef41027363..5d35811368b39a 100644 --- 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 @@ -85,6 +85,7 @@ import { } from './specific_attributes/eql_attributes'; import { Threshold } from './specific_attributes/threshold_attributes'; import { HistoryWindowStart, NewTermsFields } from './specific_attributes/new_terms_attributes'; +import { AlertSuppression } from './specific_attributes/query_attributes'; import { buildRuleSchemas } from './build_rule_schemas'; @@ -302,6 +303,7 @@ const querySchema = buildRuleSchemas({ filters: RuleFilterArray, saved_id, response_actions: ResponseActionArray, + alert_suppression: AlertSuppression, }, defaultable: { query: RuleQuery, @@ -340,6 +342,7 @@ const savedQuerySchema = buildRuleSchemas({ query: RuleQuery, filters: RuleFilterArray, response_actions: ResponseActionArray, + alert_suppression: AlertSuppression, }, defaultable: { language: t.keyof({ kuery: null, lucene: null }), diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/query_attributes.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/query_attributes.ts new file mode 100644 index 00000000000000..4edb5c6885af16 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/query_attributes.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; +import { LimitedSizeArray } from '@kbn/securitysolution-io-ts-types'; + +export const AlertSuppressionGroupBy = LimitedSizeArray({ + codec: t.string, + minSize: 1, + maxSize: 3, +}); + +/** + * Schema for fields relating to alert suppression, which enables limiting the number of alerts per entity. + * e.g. group_by: ['host.name'] would create only one alert per value of host.name. The created alert + * contains metadata about how many other candidate alerts with the same host.name value were suppressed. + */ +export type AlertSuppression = t.TypeOf; +export const AlertSuppression = t.exact( + t.type({ + group_by: AlertSuppressionGroupBy, + }) +); + +export const minimumLicenseForSuppression = 'platinum'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/8.6.0/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/8.6.0/index.ts new file mode 100644 index 00000000000000..3bf66b5e31ec66 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/8.6.0/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AlertWithCommonFields800 } from '@kbn/rule-registry-plugin/common/schemas/8.0.0'; +import type { + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_DOCS_COUNT, +} from '@kbn/rule-data-utils'; + +import type { BaseFields840, DetectionAlert840 } from '../8.4.0'; + +/* DO NOT MODIFY THIS SCHEMA TO ADD NEW FIELDS. These types represent the alerts that shipped in 8.6.0. +Any changes to these types should be bug fixes so the types more accurately represent the alerts from 8.6.0. +If you are adding new fields for a new release of Kibana, create a new sibling folder to this one +for the version to be released and add the field(s) to the schema in that folder. +Then, update `../index.ts` to import from the new folder that has the latest schemas, add the +new schemas to the union of all alert schemas, and re-export the new schemas as the `*Latest` schemas. +*/ + +export interface SuppressionFields860 extends BaseFields840 { + [ALERT_SUPPRESSION_TERMS]: Array<{ field: string; value: string | number | null }>; + [ALERT_SUPPRESSION_START]: Date; + [ALERT_SUPPRESSION_END]: Date; + [ALERT_SUPPRESSION_DOCS_COUNT]: number; +} + +export type SuppressionAlert860 = AlertWithCommonFields800; + +export type DetectionAlert860 = DetectionAlert840 | SuppressionAlert860; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/index.ts index a1a0a9079234ae..93436ffa52d6b1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/alerts/index.ts @@ -17,16 +17,19 @@ import type { NewTermsFields840, } from './8.4.0'; +import type { DetectionAlert860, SuppressionFields860 } from './8.6.0'; + // When new Alert schemas are created for new Kibana versions, add the DetectionAlert type from the new version // here, e.g. `export type DetectionAlert = DetectionAlert800 | DetectionAlert820` if a new schema is created in 8.2.0 -export type DetectionAlert = DetectionAlert800 | DetectionAlert840; +export type DetectionAlert = DetectionAlert800 | DetectionAlert840 | DetectionAlert860; export type { Ancestor840 as AncestorLatest, BaseFields840 as BaseFieldsLatest, - DetectionAlert840 as DetectionAlertLatest, + DetectionAlert860 as DetectionAlertLatest, WrappedFields840 as WrappedFieldsLatest, EqlBuildingBlockFields840 as EqlBuildingBlockFieldsLatest, EqlShellFields840 as EqlShellFieldsLatest, NewTermsFields840 as NewTermsFieldsLatest, + SuppressionFields860 as SuppressionFieldsLatest, }; 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 706283288f58c0..b5923bb4f3b36a 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -57,7 +57,11 @@ const SavedColumnHeaderRuntimeType = runtimeTypes.partial({ const SavedDataProviderQueryMatchBasicRuntimeType = runtimeTypes.partial({ field: unionWithNullType(runtimeTypes.string), displayField: unionWithNullType(runtimeTypes.string), - value: unionWithNullType(runtimeTypes.string), + value: runtimeTypes.union([ + runtimeTypes.null, + runtimeTypes.string, + runtimeTypes.array(runtimeTypes.string), + ]), displayValue: unionWithNullType(runtimeTypes.string), operator: unionWithNullType(runtimeTypes.string), }); @@ -652,7 +656,7 @@ export interface DataProviderResult { export interface QueryMatchResult { field?: Maybe; displayField?: Maybe; - value?: Maybe; + value?: Maybe; displayValue?: Maybe; operator?: Maybe; } diff --git a/x-pack/plugins/security_solution/common/utils/risk_score_modules.ts b/x-pack/plugins/security_solution/common/utils/risk_score_modules.ts index d08f6b05432d68..5c2d6d2a6af262 100644 --- a/x-pack/plugins/security_solution/common/utils/risk_score_modules.ts +++ b/x-pack/plugins/security_solution/common/utils/risk_score_modules.ts @@ -10,7 +10,7 @@ import { RiskScoreEntity, RiskScoreFields } from '../search_strategy'; import type { Pipeline, Processor } from '../types/risk_scores'; /** - * * Since 8.5, all the transforms, scripts, + * Aside from 8.4, all the transforms, scripts, * and ingest pipelines (and dashboard saved objects) are created with spaceId * so they won't affect each other across different spaces. */ @@ -45,7 +45,7 @@ export const getRiskScoreReduceScriptId = (riskScoreEntity: RiskScoreEntity, spa `ml_${riskScoreEntity}riskscore_reduce_script_${spaceId}`; /** - * These scripts and Ingest pipeline were not space awared before 8.5. + * These scripts and Ingest pipeline were not space aware in 8.4 * They were shared across spaces and therefore affected each other. * New scripts and ingest pipeline are all independent in each space, so these ids * are Deprecated. diff --git a/x-pack/plugins/security_solution/cypress/data/detection_engine.ts b/x-pack/plugins/security_solution/cypress/data/detection_engine.ts new file mode 100644 index 00000000000000..cdd72af22b7855 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/data/detection_engine.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + RiskScore, + RuleInterval, + RuleIntervalFrom, + Severity, + Threat, + ThreatSubtechnique, + ThreatTechnique, +} from '@kbn/securitysolution-io-ts-alerting-types'; + +import type { + IndexPatternArray, + InvestigationGuide, + RuleDescription, + RuleFalsePositiveArray, + RuleQuery, + RuleName, + RuleReferenceArray, + RuleTagArray, +} from '../../common/detection_engine/rule_schema'; + +interface RuleFields { + defaultIndexPatterns: IndexPatternArray; + falsePositives: RuleFalsePositiveArray; + investigationGuide: InvestigationGuide; + referenceUrls: RuleReferenceArray; + riskScore: RiskScore; + ruleDescription: RuleDescription; + ruleInterval: RuleInterval; + ruleIntervalFrom: RuleIntervalFrom; + ruleQuery: RuleQuery; + ruleName: RuleName; + ruleTags: RuleTagArray; + ruleSeverity: Severity; + threat: Threat; + threatSubtechnique: ThreatSubtechnique; + threatTechnique: ThreatTechnique; +} + +export const ruleFields: RuleFields = { + defaultIndexPatterns: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'traces-apm*', + 'winlogbeat-*', + '-*elastic-cloud-logs-*', + ], + falsePositives: ['False1', 'False2'], + investigationGuide: '# test markdown', + referenceUrls: ['http://example.com/', 'https://example.com/'], + riskScore: 17, + ruleDescription: 'The rule description', + ruleInterval: '5m', + ruleIntervalFrom: '50000h', + ruleQuery: 'host.name: *', + ruleName: 'Test Rule', + ruleTags: ['test', 'newRule'], + ruleSeverity: 'high', + threat: { + framework: 'MITRE ATT&CK', + tactic: { + name: 'Credential Access', + id: 'TA0006', + reference: 'https://attack.mitre.org/tactics/TA0006', + }, + }, + threatSubtechnique: { + name: '/etc/passwd and /etc/shadow', + id: 'T1003.008', + reference: 'https://attack.mitre.org/techniques/T1003/008', + }, + threatTechnique: { + id: 'T1003', + name: 'OS Credential Dumping', + reference: 'https://attack.mitre.org/techniques/T1003', + }, +}; diff --git a/x-pack/plugins/security_solution/cypress/e2e/dashboards/enable_risk_score.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/dashboards/enable_risk_score.cy.ts index 94cf890ba5dd8e..bb30a0f40a45ec 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/dashboards/enable_risk_score.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/dashboards/enable_risk_score.cy.ts @@ -40,14 +40,14 @@ describe('Enable risk scores', () => { }); beforeEach(() => { - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId, deleteAll: true }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId, deleteAll: true }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); visit(ENTITY_ANALYTICS_URL); }); afterEach(() => { - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId, deleteAll: true }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId, deleteAll: true }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); }); it('shows enable host risk button', () => { diff --git a/x-pack/plugins/security_solution/cypress/e2e/dashboards/upgrade_risk_score.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/dashboards/upgrade_risk_score.cy.ts index bcbc85849c1661..1a6c5f294caba4 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/dashboards/upgrade_risk_score.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/dashboards/upgrade_risk_score.cy.ts @@ -43,29 +43,21 @@ describe('Upgrade risk scores', () => { }); beforeEach(() => { - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId, deleteAll: true }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId, deleteAll: true }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); installLegacyRiskScoreModule(RiskScoreEntity.host, spaceId); installLegacyRiskScoreModule(RiskScoreEntity.user, spaceId); visit(ENTITY_ANALYTICS_URL); }); - afterEach(() => { - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId, deleteAll: true }); - deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId, deleteAll: true }); - }); - - it('shows upgrade host risk button', () => { + it('shows upgrade risk button for host and user', () => { cy.get(UPGRADE_HOST_RISK_SCORE_BUTTON).should('be.visible'); + cy.get(UPGRADE_USER_RISK_SCORE_BUTTON).should('be.visible'); }); - it('should show a confirmation modal for upgrading host risk score', () => { + it('should show a confirmation modal for upgrading host risk score and display a link to host risk score Elastic doc', () => { clickUpgradeRiskScore(RiskScoreEntity.host); cy.get(UPGRADE_CONFIRMATION_MODAL(RiskScoreEntity.host)).should('exist'); - }); - - it('display a link to host risk score Elastic doc', () => { - clickUpgradeRiskScore(RiskScoreEntity.host); cy.get(UPGRADE_CANCELLATION_BUTTON) .get(`${UPGRADE_CONFIRMATION_MODAL(RiskScoreEntity.host)} a`) @@ -76,51 +68,9 @@ describe('Upgrade risk scores', () => { }); }); - it('should upgrade host risk score successfully', () => { - clickUpgradeRiskScore(RiskScoreEntity.host); - - interceptUpgradeRiskScoreModule(RiskScoreEntity.host); - - clickUpgradeRiskScoreConfirmed(); - waitForUpgradeRiskScoreModule(); - - cy.get(UPGRADE_HOST_RISK_SCORE_BUTTON).should('be.disabled'); - - cy.get(RISK_SCORE_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.host)).should('exist'); - cy.get(RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.host)).should('exist'); - - cy.get(UPGRADE_HOST_RISK_SCORE_BUTTON).should('not.exist'); - getTransformState(getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId)).then((res) => { - expect(res.status).to.eq(200); - expect(res.body.transforms[0].id).to.eq( - getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId) - ); - expect(res.body.transforms[0].state).to.eq('started'); - }); - getTransformState(getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId)).then((res) => { - expect(res.status).to.eq(200); - expect(res.body.transforms[0].id).to.eq( - getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId) - ); - expect(res.body.transforms[0].state).to.eq('started'); - }); - findSavedObjects(RiskScoreEntity.host, spaceId).then((res) => { - expect(res.status).to.eq(200); - expect(res.body.saved_objects.length).to.eq(11); - }); - }); - - it('shows upgrade user risk button', () => { - cy.get(UPGRADE_USER_RISK_SCORE_BUTTON).should('be.visible'); - }); - - it('should show a confirmation modal for upgrading user risk score', () => { + it('should show a confirmation modal for upgrading user risk score and display a link to user risk score Elastic doc', () => { clickUpgradeRiskScore(RiskScoreEntity.user); cy.get(UPGRADE_CONFIRMATION_MODAL(RiskScoreEntity.user)).should('exist'); - }); - - it('display a link to user risk score Elastic doc', () => { - clickUpgradeRiskScore(RiskScoreEntity.user); cy.get(UPGRADE_CANCELLATION_BUTTON) .get(`${UPGRADE_CONFIRMATION_MODAL(RiskScoreEntity.user)} a`) @@ -130,36 +80,102 @@ describe('Upgrade risk scores', () => { ); }); }); +}); - it('should upgrade user risk score successfully', () => { - clickUpgradeRiskScore(RiskScoreEntity.user); - interceptUpgradeRiskScoreModule(RiskScoreEntity.user); - clickUpgradeRiskScoreConfirmed(); - waitForUpgradeRiskScoreModule(); - cy.get(UPGRADE_USER_RISK_SCORE_BUTTON).should('be.disabled'); - - cy.get(RISK_SCORE_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.user)).should('exist'); - cy.get(RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.user)).should('exist'); - - cy.get(UPGRADE_USER_RISK_SCORE_BUTTON).should('not.exist'); - getTransformState(getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId)).then((res) => { - expect(res.status).to.eq(200); - expect(res.body.transforms[0].id).to.eq( - getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId) - ); - expect(res.body.transforms[0].state).to.eq('started'); +const versions: Array<'8.3' | '8.4'> = ['8.3', '8.4']; +versions.forEach((version) => + describe(`handles version ${version} upgrades`, () => { + before(() => { + cleanKibana(); + login(); + createCustomRuleEnabled(getNewRule(), 'rule1'); + }); + + beforeEach(() => { + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); + installLegacyRiskScoreModule(RiskScoreEntity.host, spaceId, version); + installLegacyRiskScoreModule(RiskScoreEntity.user, spaceId, version); + visit(ENTITY_ANALYTICS_URL); }); - getTransformState(getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId)).then((res) => { - expect(res.status).to.eq(200); - expect(res.body.transforms[0].id).to.eq( - getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId) + + afterEach(() => { + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId }); + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId }); + }); + + it('should upgrade host risk score successfully', () => { + clickUpgradeRiskScore(RiskScoreEntity.host); + + interceptUpgradeRiskScoreModule(RiskScoreEntity.host, version); + + clickUpgradeRiskScoreConfirmed(); + waitForUpgradeRiskScoreModule(); + + cy.get(UPGRADE_HOST_RISK_SCORE_BUTTON).should('be.disabled'); + + cy.get(RISK_SCORE_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.host)).should('exist'); + cy.get(RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.host)).should( + 'exist' + ); + + cy.get(UPGRADE_HOST_RISK_SCORE_BUTTON).should('not.exist'); + getTransformState(getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId)).then((res) => { + expect(res.status).to.eq(200); + expect(res.body.transforms[0].id).to.eq( + getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId) + ); + expect(res.body.transforms[0].state).to.eq('started'); + }); + getTransformState(getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId)).then( + (res) => { + expect(res.status).to.eq(200); + expect(res.body.transforms[0].id).to.eq( + getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId) + ); + expect(res.body.transforms[0].state).to.eq('started'); + } ); - expect(res.body.transforms[0].state).to.eq('started'); + findSavedObjects(RiskScoreEntity.host, spaceId).then((res) => { + expect(res.status).to.eq(200); + expect(res.body.saved_objects.length).to.eq(11); + }); }); - findSavedObjects(RiskScoreEntity.user, spaceId).then((res) => { - expect(res.status).to.eq(200); - expect(res.body.saved_objects.length).to.eq(11); + it('should upgrade user risk score successfully', () => { + clickUpgradeRiskScore(RiskScoreEntity.user); + interceptUpgradeRiskScoreModule(RiskScoreEntity.user); + clickUpgradeRiskScoreConfirmed(); + waitForUpgradeRiskScoreModule(); + cy.get(UPGRADE_USER_RISK_SCORE_BUTTON).should('be.disabled'); + + cy.get(RISK_SCORE_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.user)).should('exist'); + cy.get(RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.user)).should( + 'exist' + ); + + cy.get(UPGRADE_USER_RISK_SCORE_BUTTON).should('not.exist'); + getTransformState(getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId)).then((res) => { + expect(res.status).to.eq(200); + expect(res.body.transforms[0].id).to.eq( + getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId) + ); + expect(res.body.transforms[0].state).to.eq('started'); + }); + getTransformState(getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId)).then( + (res) => { + expect(res.status).to.eq(200); + expect(res.body.transforms[0].id).to.eq( + getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId) + ); + expect(res.body.transforms[0].state).to.eq('started'); + } + ); + + findSavedObjects(RiskScoreEntity.user, spaceId).then((res) => { + expect(res.status).to.eq(200); + expect(res.body.saved_objects.length).to.eq(11); + }); }); - }); -}); + }) +); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts index b81806e2ce65f0..b5869987f4d584 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { formatMitreAttackDescription } from '../../helpers/rules'; -import type { Mitre } from '../../objects/rule'; +import { ruleFields } from '../../data/detection_engine'; import { getNewRule, getExistingRule, @@ -14,7 +13,7 @@ import { getEditedRule, getNewOverrideRule, } from '../../objects/rule'; -import type { CompleteTimeline } from '../../objects/timeline'; +import { getTimeline } from '../../objects/timeline'; import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts'; import { @@ -56,7 +55,6 @@ import { INDEX_PATTERNS_DETAILS, INVESTIGATION_NOTES_MARKDOWN, INVESTIGATION_NOTES_TOGGLE, - MITRE_ATTACK_DETAILS, REFERENCE_URLS_DETAILS, RISK_SCORE_DETAILS, RULE_NAME_HEADER, @@ -66,6 +64,9 @@ import { SEVERITY_DETAILS, TAGS_DETAILS, TIMELINE_TEMPLATE_DETAILS, + THREAT_TACTIC, + THREAT_TECHNIQUE, + THREAT_SUBTECHNIQUE, } from '../../screens/rule_details'; import { @@ -82,14 +83,26 @@ import { createTimeline } from '../../tasks/api_calls/timelines'; import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; import { addEmailConnectorAndRuleAction } from '../../tasks/common/rule_actions'; import { + continueWithNextSection, createAndEnableRule, + expandAdvancedSettings, fillAboutRule, - fillAboutRuleAndContinue, - fillDefineCustomRuleAndContinue, - fillScheduleRuleAndContinue, + fillDescription, + fillFalsePositiveExamples, + fillFrom, + fillNote, + fillReferenceUrls, + fillRiskScore, + fillRuleName, + fillRuleTags, + fillSeverity, + fillThreat, + fillThreatSubtechnique, + fillThreatTechnique, goToAboutStepTab, goToActionsStepTab, goToScheduleStepTab, + importSavedQuery, waitForAlertsToPopulate, waitForTheRuleToBeExecuted, } from '../../tasks/create_new_rule'; @@ -105,98 +118,125 @@ describe('Custom query rules', () => { login(); }); describe('Custom detection rules creation', () => { - const expectedUrls = getNewRule().referenceUrls?.join(''); - const expectedFalsePositives = getNewRule().falsePositivesExamples?.join(''); - const expectedTags = getNewRule().tags?.join(''); - const mitreAttack = getNewRule().mitre as Mitre[]; - const expectedMitre = formatMitreAttackDescription(mitreAttack); const expectedNumberOfRules = 1; beforeEach(() => { - const timeline = getNewRule().timeline as CompleteTimeline; deleteAlertsAndRules(); - createTimeline(timeline).then((response) => { - cy.wrap({ - ...getNewRule(), - timeline: { - ...timeline, - id: response.body.data.persistTimeline.timeline.savedObjectId, - }, - }).as('rule'); - }); + createTimeline(getTimeline()) + .then((response) => { + return response.body.data.persistTimeline.timeline.savedObjectId; + }) + .as('timelineId'); }); it('Creates and enables a new rule', function () { visit(RULE_CREATION); - fillDefineCustomRuleAndContinue(this.rule); - fillAboutRuleAndContinue(this.rule); - fillScheduleRuleAndContinue(this.rule); + + cy.log('Filling define section'); + importSavedQuery(this.timelineId); + continueWithNextSection(); + + cy.log('Filling about section'); + fillRuleName(); + fillDescription(); + fillSeverity(); + fillRiskScore(); + fillRuleTags(); + expandAdvancedSettings(); + fillReferenceUrls(); + fillFalsePositiveExamples(); + fillThreat(); + fillThreatTechnique(); + fillThreatSubtechnique(); + fillNote(); + continueWithNextSection(); + + cy.log('Filling schedule section'); + fillFrom(); // expect define step to repopulate cy.get(DEFINE_EDIT_BUTTON).click(); - cy.get(CUSTOM_QUERY_INPUT).should('have.value', this.rule.customQuery); + cy.get(CUSTOM_QUERY_INPUT).should('have.value', ruleFields.ruleQuery); cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); cy.get(DEFINE_CONTINUE_BUTTON).should('not.exist'); // expect about step to populate cy.get(ABOUT_EDIT_BUTTON).click(); - cy.get(RULE_NAME_INPUT).invoke('val').should('eql', this.rule.name); + cy.get(RULE_NAME_INPUT).invoke('val').should('eql', ruleFields.ruleName); cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true }); cy.get(ABOUT_CONTINUE_BTN).should('not.exist'); cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true }); createAndEnableRule(); + cy.log('Asserting we have a new rule created'); cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); + cy.log('Asserting rule view in rules list'); cy.get(RULES_TABLE).find(RULES_ROW).should('have.length', expectedNumberOfRules); - cy.get(RULE_NAME).should('have.text', this.rule.name); - cy.get(RISK_SCORE).should('have.text', this.rule.riskScore); - cy.get(SEVERITY).should('have.text', this.rule.severity); + cy.get(RULE_NAME).should('have.text', ruleFields.ruleName); + cy.get(RISK_SCORE).should('have.text', ruleFields.riskScore); + cy.get(SEVERITY) + .invoke('text') + .then((text) => { + cy.wrap(text.toLowerCase()).should('equal', ruleFields.ruleSeverity); + }); cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); goToRuleDetails(); - cy.get(RULE_NAME_HEADER).should('contain', `${this.rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', this.rule.description); + cy.log('Asserting rule details'); + cy.get(RULE_NAME_HEADER).should('contain', ruleFields.ruleName); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', ruleFields.ruleDescription); cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', this.rule.severity); - getDetails(RISK_SCORE_DETAILS).should('have.text', this.rule.riskScore); + getDetails(SEVERITY_DETAILS) + .invoke('text') + .then((text) => { + cy.wrap(text.toLowerCase()).should('equal', ruleFields.ruleSeverity); + }); + getDetails(RISK_SCORE_DETAILS).should('have.text', ruleFields.riskScore); getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); - }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + expect(removeExternalLinkText(details.text())).equal(ruleFields.referenceUrls.join('')); }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); + getDetails(FALSE_POSITIVES_DETAILS).should('have.text', ruleFields.falsePositives.join('')); + getDetails(TAGS_DETAILS).should('have.text', ruleFields.ruleTags.join('')); }); + cy.get(THREAT_TACTIC).should( + 'contain', + `${ruleFields.threat.tactic.name} (${ruleFields.threat.tactic.id})` + ); + cy.get(THREAT_TECHNIQUE).should( + 'contain', + `${ruleFields.threatTechnique.name} (${ruleFields.threatTechnique.id})` + ); + cy.get(THREAT_SUBTECHNIQUE).should( + 'contain', + `${ruleFields.threatSubtechnique.name} (${ruleFields.threatSubtechnique.id})` + ); cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join('')); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery); + getDetails(INDEX_PATTERNS_DETAILS).should( + 'have.text', + ruleFields.defaultIndexPatterns.join('') + ); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', ruleFields.ruleQuery); getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query'); getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); }); cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS).should( - 'have.text', - `${getNewRule().runsEvery?.interval}${getNewRule().runsEvery?.type}` - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should( - 'have.text', - `${getNewRule().lookBack?.interval}${getNewRule().lookBack?.type}` - ); + getDetails(RUNS_EVERY_DETAILS).should('have.text', ruleFields.ruleInterval); + getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should('have.text', ruleFields.ruleIntervalFrom); }); waitForTheRuleToBeExecuted(); waitForAlertsToPopulate(); + cy.log('Asserting that alerts have been generated after the creation'); cy.get(NUMBER_OF_ALERTS) .invoke('text') .should('match', /^[1-9].+$/); // Any number of alerts - cy.get(ALERT_GRID_CELL).contains(this.rule.name); + cy.get(ALERT_GRID_CELL).contains(ruleFields.ruleName); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts index c7e84d6824561d..cf35a51e498659 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts @@ -38,7 +38,7 @@ describe('risk tab', () => { it('renders the table', () => { kqlSearch('host.name: "siem-kibana" {enter}'); cy.get(HOST_BY_RISK_TABLE_CELL).eq(3).should('have.text', 'siem-kibana'); - cy.get(HOST_BY_RISK_TABLE_CELL).eq(4).should('have.text', '21.00'); + cy.get(HOST_BY_RISK_TABLE_CELL).eq(4).should('have.text', '21'); cy.get(HOST_BY_RISK_TABLE_CELL).eq(5).should('have.text', 'Low'); clearSearchBar(); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/timelines/notes_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/timelines/notes_tab.cy.ts index c20a4ae39026d0..a6691225808ef9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/timelines/notes_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/timelines/notes_tab.cy.ts @@ -13,6 +13,7 @@ import { NOTES_LINK, NOTES_TEXT, NOTES_TEXT_AREA, + MARKDOWN_INVESTIGATE_BUTTON, } from '../../screens/timeline'; import { createTimeline } from '../../tasks/api_calls/timelines'; @@ -84,4 +85,11 @@ describe('Timeline notes tab', () => { cy.get(NOTES_LINK).last().should('have.text', `${text}(opens in a new tab or window)`); cy.get(NOTES_LINK).last().click(); }); + + it('should render insight query from markdown', () => { + addNotesToTimeline( + `!{insight{"description":"2 top level OR providers, 1 nested AND","label":"test insight", "providers": [[{ "field": "event.id", "value": "kibana.alert.original_event.id", "type": "parameter" }], [{ "field": "event.category", "value": "network", "type": "literal" }, {"field": "process.pid", "value": "process.pid", "type": "parameter"}]]}}` + ); + cy.get(MARKDOWN_INVESTIGATE_BUTTON).should('exist'); + }); }); 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 5452d59b68c070..32c6a336b90964 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 @@ -22,6 +22,7 @@ export const EDIT_RULE_ACTION_BTN = '[data-test-subj="editRuleAction"]'; export const DUPLICATE_RULE_ACTION_BTN = '[data-test-subj="duplicateRuleAction"]'; export const DUPLICATE_RULE_MENU_PANEL_BTN = '[data-test-subj="rules-details-duplicate-rule"]'; +export const CONFIRM_DUPLICATE_RULE = '[data-test-subj="confirmModalConfirmButton"]'; export const ENABLE_RULE_BULK_BTN = '[data-test-subj="enableRuleBulk"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index 0d14673f4cf2b6..c57e2c603e4618 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -72,6 +72,8 @@ export const DATA_VIEW_COMBO_BOX = export const DATA_VIEW_OPTION = '[data-test-subj="rule-index-toggle-dataView"]'; +export const CONTINUE_BUTTON = '[data-test-subj$=-continue]'; + export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]'; export const DEFINE_EDIT_BUTTON = '[data-test-subj="edit-define-rule"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index 606ee4ae7a043c..05e99f2e97c3b3 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -125,3 +125,9 @@ export const DEFINE_RULE_PANEL_PROGRESS = '[data-test-subj="defineRule"] [data-test-subj="stepPanelProgress"]'; export const EDIT_RULE_SETTINGS_LINK = '[data-test-subj="editRuleSettingsLink"]'; + +export const THREAT_TACTIC = '[data-test-subj="threatTacticLink"]'; + +export const THREAT_TECHNIQUE = '[data-test-subj="threatTechniqueLink"]'; + +export const THREAT_SUBTECHNIQUE = '[data-test-subj="threatSubtechniqueLink"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 529847261e06dc..59c8d6a4103f70 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -90,6 +90,9 @@ export const NOTES_AUTHOR = '.euiCommentEvent__headerUsername'; export const NOTES_LINK = '[data-test-subj="markdown-link"]'; +export const MARKDOWN_INVESTIGATE_BUTTON = + '[data-test-subj="insight-investigate-in-timeline-button"]'; + export const OPEN_TIMELINE_ICON = '[data-test-subj="open-timeline-button"]'; export const OPEN_TIMELINE_MODAL = '[data-test-subj="open-timeline-modal"]'; 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 2bc4badf3cfa1f..836eae6076f17e 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 @@ -31,6 +31,7 @@ import { DUPLICATE_RULE_ACTION_BTN, DUPLICATE_RULE_MENU_PANEL_BTN, DUPLICATE_RULE_BULK_BTN, + CONFIRM_DUPLICATE_RULE, RULES_ROW, SELECT_ALL_RULES_BTN, MODAL_CONFIRMATION_BTN, @@ -80,6 +81,7 @@ export const duplicateFirstRule = () => { cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true }); cy.get(DUPLICATE_RULE_ACTION_BTN).should('be.visible'); cy.get(DUPLICATE_RULE_ACTION_BTN).click(); + cy.get(CONFIRM_DUPLICATE_RULE).click(); }; /** @@ -96,6 +98,7 @@ export const duplicateRuleFromMenu = () => { // Because of a fade effect and fast clicking this can produce more than one click cy.get(DUPLICATE_RULE_MENU_PANEL_BTN).pipe(click); + cy.get(CONFIRM_DUPLICATE_RULE).click(); }; /** @@ -138,6 +141,7 @@ export const duplicateSelectedRules = () => { cy.log('Duplicate selected rules'); cy.get(BULK_ACTIONS_BTN).click({ force: true }); cy.get(DUPLICATE_RULE_BULK_BTN).click(); + cy.get(CONFIRM_DUPLICATE_RULE).click(); }; export const enableSelectedRules = () => { diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/index.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/index.ts index 4429bfae8820a8..4e6edd139ec8f6 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/index.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/index.ts @@ -50,27 +50,22 @@ import { createIngestPipeline, deleteRiskScoreIngestPipelines } from './ingest_p import { deleteSavedObjects } from './saved_objects'; import { createStoredScript, deleteStoredScripts } from './stored_scripts'; -/** - * @deleteAll: If set to true, it deletes both old and new version. - * If set to false, it deletes legacy version only. - */ export const deleteRiskScore = ({ riskScoreEntity, spaceId, - deleteAll, }: { riskScoreEntity: RiskScoreEntity; spaceId?: string; - deleteAll: boolean; }) => { const transformIds = [ getRiskScorePivotTransformId(riskScoreEntity, spaceId), getRiskScoreLatestTransformId(riskScoreEntity, spaceId), ]; const legacyIngestPipelineNames = [getLegacyIngestPipelineName(riskScoreEntity)]; - const ingestPipelinesNames = deleteAll - ? [...legacyIngestPipelineNames, getIngestPipelineName(riskScoreEntity, spaceId)] - : legacyIngestPipelineNames; + const ingestPipelinesNames = [ + ...legacyIngestPipelineNames, + getIngestPipelineName(riskScoreEntity, spaceId), + ]; const legacyScriptIds = [ ...(riskScoreEntity === RiskScoreEntity.host @@ -80,53 +75,57 @@ export const deleteRiskScore = ({ getLegacyRiskScoreMapScriptId(riskScoreEntity), getLegacyRiskScoreReduceScriptId(riskScoreEntity), ]; - const scripts = deleteAll - ? [ - ...legacyScriptIds, - ...(riskScoreEntity === RiskScoreEntity.host - ? [getRiskScoreInitScriptId(riskScoreEntity, spaceId)] - : []), - getRiskScoreLevelScriptId(riskScoreEntity, spaceId), - getRiskScoreMapScriptId(riskScoreEntity, spaceId), - getRiskScoreReduceScriptId(riskScoreEntity, spaceId), - ] - : legacyScriptIds; + const scripts = [ + ...legacyScriptIds, + ...(riskScoreEntity === RiskScoreEntity.host + ? [getRiskScoreInitScriptId(riskScoreEntity, spaceId)] + : []), + getRiskScoreLevelScriptId(riskScoreEntity, spaceId), + getRiskScoreMapScriptId(riskScoreEntity, spaceId), + getRiskScoreReduceScriptId(riskScoreEntity, spaceId), + ]; deleteTransforms(transformIds); deleteRiskScoreIngestPipelines(ingestPipelinesNames); deleteStoredScripts(scripts); - deleteSavedObjects(`${riskScoreEntity}RiskScoreDashboards`, deleteAll); + deleteSavedObjects(`${riskScoreEntity}RiskScoreDashboards`); deleteRiskScoreIndicies(riskScoreEntity, spaceId); }; -const installLegacyHostRiskScoreModule = (spaceId: string) => { +/** + * Scripts id and ingest pipeline id do not have Space ID appended in 8.4. + * Scripts id and ingest pipeline id in 8.3 and after 8.5 do. + */ +const installLegacyHostRiskScoreModule = (spaceId: string, version?: '8.3' | '8.4') => { /** * Step 1 Upload script: ml_hostriskscore_levels_script */ - createStoredScript(getLegacyRiskHostCreateLevelScriptOptions()) + createStoredScript(getLegacyRiskHostCreateLevelScriptOptions(version)) .then(() => { /** * Step 2 Upload script: ml_hostriskscore_init_script */ - return createStoredScript(getLegacyRiskHostCreateInitScriptOptions()); + return createStoredScript(getLegacyRiskHostCreateInitScriptOptions(version)); }) .then(() => { /** * Step 3 Upload script: ml_hostriskscore_map_script */ - return createStoredScript(getLegacyRiskHostCreateMapScriptOptions()); + return createStoredScript(getLegacyRiskHostCreateMapScriptOptions(version)); }) .then(() => { /** * Step 4 Upload script: ml_hostriskscore_reduce_script */ - return createStoredScript(getLegacyRiskHostCreateReduceScriptOptions()); + return createStoredScript(getLegacyRiskHostCreateReduceScriptOptions(version)); }) .then(() => { /** * Step 5 Upload the ingest pipeline: ml_hostriskscore_ingest_pipeline */ - return createIngestPipeline(getLegacyRiskScoreIngestPipelineOptions(RiskScoreEntity.host)); + return createIngestPipeline( + getLegacyRiskScoreIngestPipelineOptions(RiskScoreEntity.host, version) + ); }) .then(() => { /** @@ -145,7 +144,7 @@ const installLegacyHostRiskScoreModule = (spaceId: string) => { */ return createTransform( getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId), - getCreateLegacyMLHostPivotTransformOptions({ spaceId }) + getCreateLegacyMLHostPivotTransformOptions({ spaceId, version }) ); }) .then(() => { @@ -188,28 +187,30 @@ const installLegacyHostRiskScoreModule = (spaceId: string) => { }); }; -const installLegacyUserRiskScoreModule = async (spaceId = 'default') => { +const installLegacyUserRiskScoreModule = async (spaceId = 'default', version?: '8.3' | '8.4') => { /** * Step 1 Upload script: ml_userriskscore_levels_script */ - createStoredScript(getLegacyRiskUserCreateLevelScriptOptions()) + createStoredScript(getLegacyRiskUserCreateLevelScriptOptions(version)) .then(() => { /** * Step 2 Upload script: ml_userriskscore_map_script */ - return createStoredScript(getLegacyRiskUserCreateMapScriptOptions()); + return createStoredScript(getLegacyRiskUserCreateMapScriptOptions(version)); }) .then(() => { /** * Step 3 Upload script: ml_userriskscore_reduce_script */ - return createStoredScript(getLegacyRiskUserCreateReduceScriptOptions()); + return createStoredScript(getLegacyRiskUserCreateReduceScriptOptions(version)); }) .then(() => { /** * Step 4 Upload ingest pipeline: ml_userriskscore_ingest_pipeline */ - return createIngestPipeline(getLegacyRiskScoreIngestPipelineOptions(RiskScoreEntity.user)); + return createIngestPipeline( + getLegacyRiskScoreIngestPipelineOptions(RiskScoreEntity.user, version) + ); }) .then(() => { /** @@ -228,7 +229,7 @@ const installLegacyUserRiskScoreModule = async (spaceId = 'default') => { */ return createTransform( getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId), - getCreateLegacyMLUserPivotTransformOptions({ spaceId }) + getCreateLegacyMLUserPivotTransformOptions({ spaceId, version }) ); }) .then(() => { @@ -272,12 +273,13 @@ const installLegacyUserRiskScoreModule = async (spaceId = 'default') => { export const installLegacyRiskScoreModule = ( riskScoreEntity: RiskScoreEntity, - spaceId = 'default' + spaceId = 'default', + version?: '8.3' | '8.4' ) => { if (riskScoreEntity === RiskScoreEntity.user) { - installLegacyUserRiskScoreModule(spaceId); + installLegacyUserRiskScoreModule(spaceId, version); } else { - installLegacyHostRiskScoreModule(spaceId); + installLegacyHostRiskScoreModule(spaceId, version); } }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/saved_objects.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/saved_objects.ts index 8cdc0bbf2c1e98..3e96bbcd2cb2c2 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/saved_objects.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/saved_objects.ts @@ -9,16 +9,13 @@ import { RISK_SCORE_SAVED_OBJECTS_URL, SAVED_OBJECTS_URL } from '../../../urls/r import type { RiskScoreEntity } from '../../risk_scores/common'; import { getRiskScoreTagName } from '../../risk_scores/saved_objects'; -export const deleteSavedObjects = ( - templateName: `${RiskScoreEntity}RiskScoreDashboards`, - deleteAll: boolean -) => { +export const deleteSavedObjects = (templateName: `${RiskScoreEntity}RiskScoreDashboards`) => { return cy.request({ method: 'post', url: `${RISK_SCORE_SAVED_OBJECTS_URL}/_bulk_delete/${templateName}`, failOnStatusCode: false, body: { - deleteAll, + deleteAll: true, }, headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 7fdc101f8b14cd..fc1f3b389bdf5c 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -5,6 +5,12 @@ * 2.0. */ +import type { + RuleIntervalFrom, + Threat, + ThreatSubtechnique, + ThreatTechnique, +} from '@kbn/securitysolution-io-ts-alerting-types'; import type { CustomRule, MachineLearningRule, @@ -97,6 +103,7 @@ import { NEW_TERMS_HISTORY_TIME_TYPE, NEW_TERMS_INPUT_AREA, ACTIONS_THROTTLE_INPUT, + CONTINUE_BUTTON, } from '../screens/create_new_rule'; import { INDEX_SELECTOR, @@ -109,6 +116,7 @@ import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; import { TIMELINE } from '../screens/timelines'; import { refreshPage } from './security_header'; import { EUI_FILTER_SELECT_ITEM, COMBO_BOX_INPUT } from '../screens/common/controls'; +import { ruleFields } from '../data/detection_engine'; export const createAndEnableRule = () => { cy.get(CREATE_AND_ENABLE_BTN).click({ force: true }); @@ -149,11 +157,16 @@ export const fillAboutRule = ( } }; -const fillNote = (note: string) => { +export const expandAdvancedSettings = () => { + cy.get(ADVANCED_SETTINGS_BTN).click({ force: true }); +}; + +export const fillNote = (note: string = ruleFields.investigationGuide) => { cy.get(INVESTIGATION_NOTES_TEXTAREA).clear({ force: true }).type(note, { force: true }); + return note; }; -const fillMitre = (mitreAttacks: Mitre[]) => { +export const fillMitre = (mitreAttacks: Mitre[]) => { let techniqueIndex = 0; let subtechniqueInputIndex = 0; mitreAttacks.forEach((mitre, tacticIndex) => { @@ -178,9 +191,32 @@ const fillMitre = (mitreAttacks: Mitre[]) => { cy.get(MITRE_ATTACK_ADD_TACTIC_BUTTON).click({ force: true }); }); + return mitreAttacks; +}; + +export const fillThreat = (threat: Threat = ruleFields.threat) => { + cy.get(MITRE_ATTACK_TACTIC_DROPDOWN).first().click({ force: true }); + cy.contains(MITRE_TACTIC, threat.tactic.name).click(); + return threat; +}; + +export const fillThreatTechnique = (technique: ThreatTechnique = ruleFields.threatTechnique) => { + cy.get(MITRE_ATTACK_ADD_TECHNIQUE_BUTTON).first().click({ force: true }); + cy.get(MITRE_ATTACK_TECHNIQUE_DROPDOWN).first().click({ force: true }); + cy.contains(MITRE_TACTIC, technique.name).click(); + return technique; +}; + +export const fillThreatSubtechnique = ( + subtechnique: ThreatSubtechnique = ruleFields.threatSubtechnique +) => { + cy.get(MITRE_ATTACK_ADD_SUBTECHNIQUE_BUTTON).first().click({ force: true }); + cy.get(MITRE_ATTACK_SUBTECHNIQUE_DROPDOWN).first().click({ force: true }); + cy.contains(MITRE_TACTIC, subtechnique.name).click(); + return subtechnique; }; -const fillFalsePositiveExamples = (falsePositives: string[]) => { +export const fillFalsePositiveExamples = (falsePositives: string[] = ruleFields.falsePositives) => { falsePositives.forEach((falsePositive, index) => { cy.get(FALSE_POSITIVES_INPUT) .eq(index) @@ -188,28 +224,49 @@ const fillFalsePositiveExamples = (falsePositives: string[]) => { .type(falsePositive, { force: true }); cy.get(ADD_FALSE_POSITIVE_BTN).click({ force: true }); }); + return falsePositives; }; -const fillSeverity = (severity: string) => { +export const importSavedQuery = (timelineId: string) => { + cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click(); + cy.get(TIMELINE(timelineId)).click(); + cy.get(CUSTOM_QUERY_INPUT).should('not.be.empty'); +}; + +export const fillRuleName = (ruleName: string = ruleFields.ruleName) => { + cy.get(RULE_NAME_INPUT).clear({ force: true }).type(ruleName, { force: true }); + return ruleName; +}; + +export const fillDescription = (description: string = ruleFields.ruleDescription) => { + cy.get(RULE_DESCRIPTION_INPUT).clear({ force: true }).type(description, { force: true }); + return description; +}; + +export const fillSeverity = (severity: string = ruleFields.ruleSeverity) => { cy.get(SEVERITY_DROPDOWN).click({ force: true }); cy.get(`#${severity.toLowerCase()}`).click(); + return severity; }; -const fillRiskScore = (riskScore: string) => { +export const fillRiskScore = (riskScore: string = ruleFields.riskScore.toString()) => { cy.get(DEFAULT_RISK_SCORE_INPUT).type(`{selectall}${riskScore}`, { force: true }); + return riskScore; }; -const fillRuleTags = (tags: string[]) => { +export const fillRuleTags = (tags: string[] = ruleFields.ruleTags) => { tags.forEach((tag) => { cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true }); }); + return tags; }; -const fillReferenceUrls = (referenceUrls: string[]) => { +export const fillReferenceUrls = (referenceUrls: string[] = ruleFields.referenceUrls) => { referenceUrls.forEach((url, index) => { cy.get(REFERENCE_URLS_INPUT).eq(index).clear({ force: true }).type(url, { force: true }); cy.get(ADD_REFERENCE_URL_BTN).click({ force: true }); }); + return referenceUrls; }; export const fillAboutRuleAndContinue = ( @@ -286,6 +343,10 @@ const fillCustomQuery = (rule: CustomRule | OverrideRule) => { } }; +export const continueWithNextSection = () => { + cy.get(CONTINUE_BUTTON).should('exist').click(); +}; + export const fillDefineCustomRuleAndContinue = (rule: CustomRule | OverrideRule) => { if (rule.dataSource.type === 'dataView') { cy.get(DATA_VIEW_OPTION).click(); @@ -308,6 +369,13 @@ export const fillScheduleRuleAndContinue = (rule: CustomRule | MachineLearningRu cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true }); }; +export const fillFrom = (from: RuleIntervalFrom = ruleFields.ruleIntervalFrom) => { + const value = from.slice(0, from.length - 1); + const type = from.slice(from.length - 1); + cy.get(LOOK_BACK_INTERVAL).type('{selectAll}').type(value); + cy.get(LOOK_BACK_TIME_TYPE).select(type); +}; + export const fillRuleAction = (rule: CustomRule) => { if (rule.actions) { cy.get(ACTIONS_THROTTLE_INPUT).select(rule.actions.throttle); @@ -614,3 +682,20 @@ export const checkLoadQueryDynamically = () => { export const uncheckLoadQueryDynamically = () => { cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).click({ force: true }).should('not.be.checked'); }; + +export const defineSection = { importSavedQuery }; +export const aboutSection = { + fillRuleName, + fillDescription, + fillSeverity, + fillRiskScore, + fillRuleTags, + expandAdvancedSettings, + fillReferenceUrls, + fillFalsePositiveExamples, + fillThreat, + fillThreatTechnique, + fillThreatSubtechnique, + fillNote, +}; +export const scheduleSection = { fillFrom }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts index 482ea64cf4a8bd..c4ceae7032ac09 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts @@ -38,8 +38,8 @@ export const deleteExceptionListWithoutRuleReference = () => { }; export const deleteExceptionListWithRuleReference = () => { - cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click(); - cy.get(EXCEPTIONS_TABLE_DELETE_BTN).first().click(); + cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).last().click(); + cy.get(EXCEPTIONS_TABLE_DELETE_BTN).last().click(); cy.get(EXCEPTIONS_TABLE_MODAL).should('exist'); cy.get(EXCEPTIONS_TABLE_MODAL_CONFIRM_BTN).first().click(); cy.get(EXCEPTIONS_TABLE_MODAL).should('not.exist'); diff --git a/x-pack/plugins/security_solution/cypress/tasks/risk_scores/index.ts b/x-pack/plugins/security_solution/cypress/tasks/risk_scores/index.ts index 4b81e4d728990a..a4da5eb67022a6 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/risk_scores/index.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/risk_scores/index.ts @@ -23,19 +23,24 @@ import { import { intercepInstallRiskScoreModule } from '../api_calls/risk_scores'; import { RiskScoreEntity } from './common'; -import { getLegacyIngestPipelineName } from './ingest_pipelines'; +import { getIngestPipelineName, getLegacyIngestPipelineName } from './ingest_pipelines'; -export const interceptUpgradeRiskScoreModule = (riskScoreEntity: RiskScoreEntity) => { +export const interceptUpgradeRiskScoreModule = ( + riskScoreEntity: RiskScoreEntity, + version?: '8.3' | '8.4' +) => { + const ingestPipelinesNames = `${getLegacyIngestPipelineName( + riskScoreEntity + )},${getIngestPipelineName(riskScoreEntity)}`; cy.intercept( `POST`, `${RISK_SCORE_SAVED_OBJECTS_URL}/_bulk_delete/${riskScoreEntity}RiskScoreDashboards` ).as('deleteDashboards'); cy.intercept(`POST`, `${TRANSFORMS_URL}/stop_transforms`).as('stopTransforms'); cy.intercept(`POST`, `${TRANSFORMS_URL}/delete_transforms`).as('deleteTransforms'); - cy.intercept( - `DELETE`, - `${INGEST_PIPELINES_URL}/${getLegacyIngestPipelineName(riskScoreEntity)}` - ).as('deleteIngestPipelines'); + cy.intercept(`DELETE`, `${INGEST_PIPELINES_URL}/${ingestPipelinesNames}`).as( + 'deleteIngestPipelines' + ); cy.intercept(`DELETE`, `${STORED_SCRIPTS_URL}/delete`).as('deleteScripts'); intercepInstallRiskScoreModule(); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/risk_scores/ingest_pipelines.ts b/x-pack/plugins/security_solution/cypress/tasks/risk_scores/ingest_pipelines.ts index d3f29323b52683..2efedeb71836e8 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/risk_scores/ingest_pipelines.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/risk_scores/ingest_pipelines.ts @@ -6,7 +6,7 @@ */ import type { RiskScoreEntity } from './common'; -import { getLegacyRiskScoreLevelScriptId } from './stored_scripts'; +import { getLegacyRiskScoreLevelScriptId, getRiskScoreLevelScriptId } from './stored_scripts'; export const getIngestPipelineName = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') => `ml_${riskScoreEntity}riskscore_ingest_pipeline_${spaceId}`; @@ -14,7 +14,10 @@ export const getIngestPipelineName = (riskScoreEntity: RiskScoreEntity, spaceId export const getLegacyIngestPipelineName = (riskScoreEntity: RiskScoreEntity) => `ml_${riskScoreEntity}riskscore_ingest_pipeline`; -export const getLegacyRiskScoreIngestPipelineOptions = (riskScoreEntity: RiskScoreEntity) => { +export const getLegacyRiskScoreIngestPipelineOptions = ( + riskScoreEntity: RiskScoreEntity, + version = '8.4' +) => { const processors = [ { set: { @@ -31,7 +34,10 @@ export const getLegacyRiskScoreIngestPipelineOptions = (riskScoreEntity: RiskSco }, { script: { - id: getLegacyRiskScoreLevelScriptId(riskScoreEntity), + id: + version === '8.4' + ? getLegacyRiskScoreLevelScriptId(riskScoreEntity) + : getRiskScoreLevelScriptId(riskScoreEntity), params: { risk_score: 'risk_stats.risk_score', }, @@ -39,7 +45,10 @@ export const getLegacyRiskScoreIngestPipelineOptions = (riskScoreEntity: RiskSco }, ]; return { - name: getLegacyIngestPipelineName(riskScoreEntity), + name: + version === '8.4' + ? getLegacyIngestPipelineName(riskScoreEntity) + : getIngestPipelineName(riskScoreEntity), processors, }; }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/risk_scores/stored_scripts.ts b/x-pack/plugins/security_solution/cypress/tasks/risk_scores/stored_scripts.ts index 61ee6ff430c9b9..0e510e1247e193 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/risk_scores/stored_scripts.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/risk_scores/stored_scripts.ts @@ -25,59 +25,74 @@ export const getLegacyRiskScoreMapScriptId = (riskScoreEntity: RiskScoreEntity) export const getLegacyRiskScoreReduceScriptId = (riskScoreEntity: RiskScoreEntity) => `ml_${riskScoreEntity}riskscore_reduce_script`; -export const getLegacyRiskHostCreateLevelScriptOptions = (stringifyScript?: boolean) => { +export const getLegacyRiskHostCreateLevelScriptOptions = (version = '8.4') => { const source = "double risk_score = (def)ctx.getByPath(params.risk_score);\nif (risk_score < 20) {\n ctx['risk'] = 'Unknown'\n}\nelse if (risk_score >= 20 && risk_score < 40) {\n ctx['risk'] = 'Low'\n}\nelse if (risk_score >= 40 && risk_score < 70) {\n ctx['risk'] = 'Moderate'\n}\nelse if (risk_score >= 70 && risk_score < 90) {\n ctx['risk'] = 'High'\n}\nelse if (risk_score >= 90) {\n ctx['risk'] = 'Critical'\n}"; return { - id: getLegacyRiskScoreLevelScriptId(RiskScoreEntity.host), + id: + version === '8.4' + ? getLegacyRiskScoreLevelScriptId(RiskScoreEntity.host) + : getRiskScoreLevelScriptId(RiskScoreEntity.host), script: { lang: 'painless', - source: stringifyScript ? JSON.stringify(source) : source, + source, }, }; }; -export const getLegacyRiskHostCreateInitScriptOptions = (stringifyScript?: boolean) => { +export const getLegacyRiskHostCreateInitScriptOptions = (version = '8.4') => { const source = 'state.rule_risk_stats = new HashMap();\nstate.host_variant_set = false;\nstate.host_variant = new String();\nstate.tactic_ids = new HashSet();'; return { - id: getLegacyRiskScoreInitScriptId(RiskScoreEntity.host), + id: + version === '8.4' + ? getLegacyRiskScoreInitScriptId(RiskScoreEntity.host) + : getRiskScoreInitScriptId(RiskScoreEntity.host), script: { lang: 'painless', - source: stringifyScript ? JSON.stringify(source) : source, + source, }, }; }; -export const getLegacyRiskHostCreateMapScriptOptions = (stringifyScript?: boolean) => { +export const getLegacyRiskHostCreateMapScriptOptions = (version = '8.4') => { const source = '// Get the host variant\nif (state.host_variant_set == false) {\n if (doc.containsKey("host.os.full") && doc["host.os.full"].size() != 0) {\n state.host_variant = doc["host.os.full"].value;\n state.host_variant_set = true;\n }\n}\n// Aggregate all the tactics seen on the host\nif (doc.containsKey("signal.rule.threat.tactic.id") && doc["signal.rule.threat.tactic.id"].size() != 0) {\n state.tactic_ids.add(doc["signal.rule.threat.tactic.id"].value);\n}\n// Get running sum of time-decayed risk score per rule name per shard\nString rule_name = doc["signal.rule.name"].value;\ndef stats = state.rule_risk_stats.getOrDefault(rule_name, [0.0,"",false]);\nint time_diff = (int)((System.currentTimeMillis() - doc["@timestamp"].value.toInstant().toEpochMilli()) / (1000.0 * 60.0 * 60.0));\ndouble risk_derate = Math.min(1, Math.exp((params.lookback_time - time_diff) / params.time_decay_constant));\nstats[0] = Math.max(stats[0], doc["signal.rule.risk_score"].value * risk_derate);\nif (stats[2] == false) {\n stats[1] = doc["kibana.alert.rule.uuid"].value;\n stats[2] = true;\n}\nstate.rule_risk_stats.put(rule_name, stats);'; return { - id: getLegacyRiskScoreMapScriptId(RiskScoreEntity.host), + id: + version === '8.4' + ? getLegacyRiskScoreMapScriptId(RiskScoreEntity.host) + : getRiskScoreMapScriptId(RiskScoreEntity.host), script: { lang: 'painless', - source: stringifyScript ? JSON.stringify(source) : source, + source, }, }; }; -export const getLegacyRiskHostCreateReduceScriptOptions = (stringifyScript?: boolean) => { +export const getLegacyRiskHostCreateReduceScriptOptions = (version = '8.4') => { const source = '// Consolidating time decayed risks and tactics from across all shards\nMap total_risk_stats = new HashMap();\nString host_variant = new String();\ndef tactic_ids = new HashSet();\nfor (state in states) {\n for (key in state.rule_risk_stats.keySet()) {\n def rule_stats = state.rule_risk_stats.get(key);\n def stats = total_risk_stats.getOrDefault(key, [0.0,"",false]);\n stats[0] = Math.max(stats[0], rule_stats[0]);\n if (stats[2] == false) {\n stats[1] = rule_stats[1];\n stats[2] = true;\n } \n total_risk_stats.put(key, stats);\n }\n if (host_variant.length() == 0) {\n host_variant = state.host_variant;\n }\n tactic_ids.addAll(state.tactic_ids);\n}\n// Consolidating individual rule risks and arranging them in decreasing order\nList risks = new ArrayList();\nfor (key in total_risk_stats.keySet()) {\n risks.add(total_risk_stats[key][0])\n}\nCollections.sort(risks, Collections.reverseOrder());\n// Calculating total host risk score\ndouble total_risk = 0.0;\ndouble risk_cap = params.max_risk * params.zeta_constant;\nfor (int i=0;i= 40 && total_norm_risk < 50) {\n total_norm_risk = 85 + (total_norm_risk - 40);\n}\nelse {\n total_norm_risk = 95 + (total_norm_risk - 50) / 10;\n}\n// Calculating multipliers to the host risk score\ndouble risk_multiplier = 1.0;\nList multipliers = new ArrayList();\n// Add a multiplier if host is a server\nif (host_variant.toLowerCase().contains("server")) {\n risk_multiplier *= params.server_multiplier;\n multipliers.add("Host is a server");\n}\n// Add multipliers based on number and diversity of tactics seen on the host\nfor (String tactic : tactic_ids) {\n multipliers.add("Tactic "+tactic);\n risk_multiplier *= 1 + params.tactic_base_multiplier * params.tactic_weights.getOrDefault(tactic, 0);\n}\n// Calculating final risk\ndouble final_risk = total_norm_risk;\nif (risk_multiplier > 1.0) {\n double prior_odds = (total_norm_risk) / (100 - total_norm_risk);\n double updated_odds = prior_odds * risk_multiplier; \n final_risk = 100 * updated_odds / (1 + updated_odds);\n}\n// Adding additional metadata\nList rule_stats = new ArrayList();\nfor (key in total_risk_stats.keySet()) {\n Map temp = new HashMap();\n temp["rule_name"] = key;\n temp["rule_risk"] = total_risk_stats[key][0];\n temp["rule_id"] = total_risk_stats[key][1];\n rule_stats.add(temp);\n}\n\nreturn ["calculated_score_norm": final_risk, "rule_risks": rule_stats, "multipliers": multipliers];'; return { - id: getLegacyRiskScoreReduceScriptId(RiskScoreEntity.host), + id: + version === '8.4' + ? getLegacyRiskScoreReduceScriptId(RiskScoreEntity.host) + : getRiskScoreReduceScriptId(RiskScoreEntity.host), script: { lang: 'painless', - source: stringifyScript ? JSON.stringify(source) : source, + source, }, }; }; -export const getLegacyRiskUserCreateLevelScriptOptions = () => { +export const getLegacyRiskUserCreateLevelScriptOptions = (version = '8.4') => { const source = "double risk_score = (def)ctx.getByPath(params.risk_score);\nif (risk_score < 20) {\n ctx['risk'] = 'Unknown'\n}\nelse if (risk_score >= 20 && risk_score < 40) {\n ctx['risk'] = 'Low'\n}\nelse if (risk_score >= 40 && risk_score < 70) {\n ctx['risk'] = 'Moderate'\n}\nelse if (risk_score >= 70 && risk_score < 90) {\n ctx['risk'] = 'High'\n}\nelse if (risk_score >= 90) {\n ctx['risk'] = 'Critical'\n}"; return { - id: getLegacyRiskScoreLevelScriptId(RiskScoreEntity.user), + id: + version === '8.4' + ? getLegacyRiskScoreLevelScriptId(RiskScoreEntity.user) + : getRiskScoreLevelScriptId(RiskScoreEntity.user), script: { lang: 'painless', source, @@ -85,11 +100,14 @@ export const getLegacyRiskUserCreateLevelScriptOptions = () => { }; }; -export const getLegacyRiskUserCreateMapScriptOptions = () => { +export const getLegacyRiskUserCreateMapScriptOptions = (version = '8.4') => { const source = '// Get running sum of risk score per rule name per shard\\\\\nString rule_name = doc["signal.rule.name"].value;\ndef stats = state.rule_risk_stats.getOrDefault(rule_name, 0.0);\nstats = doc["signal.rule.risk_score"].value;\nstate.rule_risk_stats.put(rule_name, stats);'; return { - id: getLegacyRiskScoreMapScriptId(RiskScoreEntity.user), + id: + version === '8.4' + ? getLegacyRiskScoreMapScriptId(RiskScoreEntity.user) + : getRiskScoreMapScriptId(RiskScoreEntity.user), script: { lang: 'painless', source, @@ -97,11 +115,14 @@ export const getLegacyRiskUserCreateMapScriptOptions = () => { }; }; -export const getLegacyRiskUserCreateReduceScriptOptions = () => { +export const getLegacyRiskUserCreateReduceScriptOptions = (version = '8.4') => { const source = '// Consolidating time decayed risks from across all shards\nMap total_risk_stats = new HashMap();\nfor (state in states) {\n for (key in state.rule_risk_stats.keySet()) {\n def rule_stats = state.rule_risk_stats.get(key);\n def stats = total_risk_stats.getOrDefault(key, 0.0);\n stats = rule_stats;\n total_risk_stats.put(key, stats);\n }\n}\n// Consolidating individual rule risks and arranging them in decreasing order\nList risks = new ArrayList();\nfor (key in total_risk_stats.keySet()) {\n risks.add(total_risk_stats[key])\n}\nCollections.sort(risks, Collections.reverseOrder());\n// Calculating total risk and normalizing it to a range\ndouble total_risk = 0.0;\ndouble risk_cap = params.max_risk * params.zeta_constant;\nfor (int i=0;i= 40 && total_norm_risk < 50) {\n total_norm_risk = 85 + (total_norm_risk - 40);\n}\nelse {\n total_norm_risk = 95 + (total_norm_risk - 50) / 10;\n}\n\nList rule_stats = new ArrayList();\nfor (key in total_risk_stats.keySet()) {\n Map temp = new HashMap();\n temp["rule_name"] = key;\n temp["rule_risk"] = total_risk_stats[key];\n rule_stats.add(temp);\n}\n\nreturn ["risk_score": total_norm_risk, "rule_risks": rule_stats];'; return { - id: getLegacyRiskScoreReduceScriptId(RiskScoreEntity.user), + id: + version === '8.4' + ? getLegacyRiskScoreReduceScriptId(RiskScoreEntity.user) + : getRiskScoreReduceScriptId(RiskScoreEntity.user), script: { lang: 'painless', source, diff --git a/x-pack/plugins/security_solution/cypress/tasks/risk_scores/transforms.ts b/x-pack/plugins/security_solution/cypress/tasks/risk_scores/transforms.ts index c1e654d530653a..51fd3156fe5153 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/risk_scores/transforms.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/risk_scores/transforms.ts @@ -8,11 +8,14 @@ import { TRANSFORMS_URL } from '../../urls/risk_score'; import { RiskScoreEntity } from './common'; import { getLatestTransformIndex, getPivotTransformIndex } from './indices'; -import { getLegacyIngestPipelineName } from './ingest_pipelines'; +import { getIngestPipelineName, getLegacyIngestPipelineName } from './ingest_pipelines'; import { getLegacyRiskScoreInitScriptId, getLegacyRiskScoreMapScriptId, getLegacyRiskScoreReduceScriptId, + getRiskScoreInitScriptId, + getRiskScoreMapScriptId, + getRiskScoreReduceScriptId, } from './stored_scripts'; const DEFAULT_ALERTS_INDEX = '.alerts-security.alerts' as const; @@ -111,13 +114,18 @@ export const deleteTransforms = (transformIds: string[]) => { export const getCreateLegacyMLHostPivotTransformOptions = ({ spaceId = 'default', + version = '8.4', }: { spaceId?: string; + version?: '8.3' | '8.4'; }) => { const options = { dest: { index: getPivotTransformIndex(RiskScoreEntity.host, spaceId), - pipeline: getLegacyIngestPipelineName(RiskScoreEntity.host), + pipeline: + version === '8.4' + ? getLegacyIngestPipelineName(RiskScoreEntity.host) + : getIngestPipelineName(RiskScoreEntity.host, spaceId), }, frequency: '1h', pivot: { @@ -131,10 +139,16 @@ export const getCreateLegacyMLHostPivotTransformOptions = ({ scripted_metric: { combine_script: 'return state', init_script: { - id: getLegacyRiskScoreInitScriptId(RiskScoreEntity.host), + id: + version === '8.4' + ? getLegacyRiskScoreInitScriptId(RiskScoreEntity.host) + : getRiskScoreInitScriptId(RiskScoreEntity.host, spaceId), }, map_script: { - id: getLegacyRiskScoreMapScriptId(RiskScoreEntity.host), + id: + version === '8.4' + ? getLegacyRiskScoreMapScriptId(RiskScoreEntity.host) + : getRiskScoreMapScriptId(RiskScoreEntity.host, spaceId), }, params: { lookback_time: 72, @@ -162,7 +176,10 @@ export const getCreateLegacyMLHostPivotTransformOptions = ({ zeta_constant: 2.612, }, reduce_script: { - id: getLegacyRiskScoreReduceScriptId(RiskScoreEntity.host), + id: + version === '8.4' + ? getLegacyRiskScoreReduceScriptId(RiskScoreEntity.host) + : getRiskScoreReduceScriptId(RiskScoreEntity.host, spaceId), }, }, }, @@ -204,13 +221,18 @@ export const getCreateLegacyMLHostPivotTransformOptions = ({ export const getCreateLegacyMLUserPivotTransformOptions = ({ spaceId = 'default', + version = '8.4', }: { spaceId?: string; + version?: '8.3' | '8.4'; }) => { const options = { dest: { index: getPivotTransformIndex(RiskScoreEntity.user, spaceId), - pipeline: getLegacyIngestPipelineName(RiskScoreEntity.user), + pipeline: + version === '8.4' + ? getLegacyIngestPipelineName(RiskScoreEntity.user) + : getIngestPipelineName(RiskScoreEntity.user), }, frequency: '1h', pivot: { @@ -225,7 +247,10 @@ export const getCreateLegacyMLUserPivotTransformOptions = ({ combine_script: 'return state', init_script: 'state.rule_risk_stats = new HashMap();', map_script: { - id: getLegacyRiskScoreMapScriptId(RiskScoreEntity.user), + id: + version === '8.4' + ? getLegacyRiskScoreMapScriptId(RiskScoreEntity.user) + : getRiskScoreMapScriptId(RiskScoreEntity.user), }, params: { max_risk: 100, @@ -233,7 +258,10 @@ export const getCreateLegacyMLUserPivotTransformOptions = ({ zeta_constant: 2.612, }, reduce_script: { - id: getLegacyRiskScoreReduceScriptId(RiskScoreEntity.user), + id: + version === '8.4' + ? getLegacyRiskScoreReduceScriptId(RiskScoreEntity.user) + : getRiskScoreReduceScriptId(RiskScoreEntity.user), }, }, }, diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 1ff04833db2a42..76a512a6fcb887 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -161,7 +161,11 @@ export const addNotesToTimeline = (notes: string) => { .then(($el) => { const notesCount = parseInt($el.text(), 10); - cy.get(NOTES_TEXT_AREA).type(notes); + cy.get(NOTES_TEXT_AREA).type(notes, { + parseSpecialCharSequences: false, + delay: 0, + force: true, + }); cy.get(ADD_NOTE_BUTTON).trigger('click'); cy.get(`${NOTES_TAB_BUTTON} .euiBadge`).should('have.text', `${notesCount + 1}`); }); diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap index a0ef37259aaa2a..5ec3b5e1581382 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap @@ -625,6 +625,26 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = }, "type": "string", }, + "nestedField.thirdAttributes": Object { + "aggregatable": false, + "category": "nestedField", + "description": "", + "example": "", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "nestedField.thirdAttributes", + "searchable": true, + "subType": Object { + "nested": Object { + "path": "nestedField", + }, + }, + "type": "date", + }, }, }, "source": Object { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx index 820488225e17b7..3c095c5ed87da4 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx @@ -5,13 +5,14 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { EuiButton, EuiButtonEmpty } from '@elastic/eui'; import type { Filter } from '@kbn/es-query'; import { useDispatch } from 'react-redux'; import { sourcererSelectors } from '../../../store'; import { InputsModelId } from '../../../store/inputs/constants'; +import type { TimeRange } from '../../../store/inputs/model'; import { inputsActions } from '../../../store/inputs'; import { updateProviders, setFilters } from '../../../../timelines/store/timeline/actions'; import { sourcererActions } from '../../../store/actions'; @@ -26,7 +27,10 @@ export const InvestigateInTimelineButton: React.FunctionComponent<{ asEmptyButton: boolean; dataProviders: DataProvider[] | null; filters?: Filter[] | null; -}> = ({ asEmptyButton, children, dataProviders, filters, ...rest }) => { + timeRange?: TimeRange; + keepDataView?: boolean; + isDisabled?: boolean; +}> = ({ asEmptyButton, children, dataProviders, filters, timeRange, keepDataView, ...rest }) => { const dispatch = useDispatch(); const getDataViewsSelector = useMemo( @@ -37,15 +41,24 @@ export const InvestigateInTimelineButton: React.FunctionComponent<{ getDataViewsSelector(state) ); + const hasTemplateProviders = + dataProviders && dataProviders.find((provider) => provider.type === 'template'); + const clearTimeline = useCreateTimeline({ timelineId: TimelineId.active, - timelineType: TimelineType.default, + timelineType: hasTemplateProviders ? TimelineType.template : TimelineType.default, }); - const configureAndOpenTimeline = React.useCallback(() => { + const configureAndOpenTimeline = useCallback(() => { if (dataProviders || filters) { // Reset the current timeline - clearTimeline(); + if (timeRange) { + clearTimeline({ + timeRange, + }); + } else { + clearTimeline(); + } if (dataProviders) { // Update the timeline's providers to match the current prevalence field query dispatch( @@ -66,17 +79,28 @@ export const InvestigateInTimelineButton: React.FunctionComponent<{ } // Only show detection alerts // (This is required so the timeline event count matches the prevalence count) - dispatch( - sourcererActions.setSelectedDataView({ - id: SourcererScopeName.timeline, - selectedDataViewId: defaultDataView.id, - selectedPatterns: [signalIndexName || ''], - }) - ); + if (!keepDataView) { + dispatch( + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.timeline, + selectedDataViewId: defaultDataView.id, + selectedPatterns: [signalIndexName || ''], + }) + ); + } // Unlock the time range from the global time range dispatch(inputsActions.removeLinkTo([InputsModelId.timeline, InputsModelId.global])); } - }, [dataProviders, clearTimeline, dispatch, defaultDataView.id, signalIndexName, filters]); + }, [ + dataProviders, + clearTimeline, + dispatch, + defaultDataView.id, + signalIndexName, + filters, + timeRange, + keepDataView, + ]); return asEmptyButton ? ( ({ +export const getDataProvider = ( + field: string, + id: string, + value: string | string[], + operator: QueryOperator = IS_OPERATOR +): DataProvider => ({ and: [], enabled: true, id: escapeDataProviderId(id), @@ -58,7 +64,8 @@ export const getDataProvider = (field: string, id: string, value: string): DataP queryMatch: { field, value, - operator: IS_OPERATOR, + operator, + displayValue: getDisplayValue(value), }, }); diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx index 58483039570270..bb780af23c82c5 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/use_hover_actions.tsx @@ -80,6 +80,20 @@ export const useHoverActions = ({ const { closeTopN, toggleTopN, isShowingTopN } = useTopNPopOver(handleClosePopOverTrigger); + const values = useMemo(() => { + const val = dataProvider.queryMatch.value; + + if (typeof val === 'number') { + return val.toString(); + } + + if (Array.isArray(val)) { + return val.map((item) => String(item)); + } + + return val; + }, [dataProvider.queryMatch.value]); + const hoverContent = useMemo(() => { // display links as additional content in the hover menu to enable keyboard // navigation of links (when the draggable contains them): @@ -110,11 +124,7 @@ export const useHoverActions = ({ showTopN={isShowingTopN} scopeId={id} toggleTopN={toggleTopN} - values={ - typeof dataProvider.queryMatch.value !== 'number' - ? dataProvider.queryMatch.value - : `${dataProvider.queryMatch.value}` - } + values={values} /> ); }, [ @@ -131,6 +141,7 @@ export const useHoverActions = ({ onFilterAdded, id, toggleTopN, + values, ]); const setContainerRef = useCallback((e: HTMLDivElement) => { diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/utils.test.ts b/x-pack/plugins/security_solution/public/common/components/hover_actions/utils.test.ts new file mode 100644 index 00000000000000..4adb83b26cddac --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/utils.test.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act, renderHook } from '@testing-library/react-hooks'; +import { useTopNPopOver } from './utils'; + +describe('useTopNPopOver', () => { + it('calls setIsPopoverVisible when toggling top N', () => { + const setIsPopoverVisible = jest.fn(); + const { + result: { + current: { toggleTopN }, + }, + } = renderHook(() => useTopNPopOver(setIsPopoverVisible)); + + act(() => { + toggleTopN(); + }); + + expect(setIsPopoverVisible).toHaveBeenCalled(); + }); + + it('sets isShowingTopN to true when toggleTopN is called', () => { + const { result } = renderHook(() => useTopNPopOver()); + + expect(result.current.isShowingTopN).toBeFalsy(); + + act(() => { + result.current.toggleTopN(); + }); + + expect(result.current.isShowingTopN).toBeTruthy(); + }); + + it('sets isShowingTopN to false when toggleTopN is called for the second time', () => { + const { result } = renderHook(() => useTopNPopOver()); + + expect(result.current.isShowingTopN).toBeFalsy(); + + act(() => { + result.current.toggleTopN(); + result.current.toggleTopN(); + }); + + expect(result.current.isShowingTopN).toBeFalsy(); + }); + + it('sets isShowingTopN to false when closeTopN is called', () => { + const { result } = renderHook(() => useTopNPopOver()); + act(() => { + // First, make isShowingTopN truthy. + result.current.toggleTopN(); + }); + expect(result.current.isShowingTopN).toBeTruthy(); + + act(() => { + result.current.closeTopN(); + }); + expect(result.current.isShowingTopN).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts index 494ecb0c6b4d0e..8a76223bb290cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts @@ -13,6 +13,7 @@ import { import * as timelineMarkdownPlugin from './timeline'; import * as osqueryMarkdownPlugin from './osquery'; +import * as insightMarkdownPlugin from './insight'; export const { uiPlugins, parsingPlugins, processingPlugins } = { uiPlugins: getDefaultEuiMarkdownUiPlugins(), @@ -23,9 +24,11 @@ export const { uiPlugins, parsingPlugins, processingPlugins } = { uiPlugins.push(timelineMarkdownPlugin.plugin); uiPlugins.push(osqueryMarkdownPlugin.plugin); +parsingPlugins.push(insightMarkdownPlugin.parser); parsingPlugins.push(timelineMarkdownPlugin.parser); parsingPlugins.push(osqueryMarkdownPlugin.parser); // This line of code is TS-compatible and it will break if [1][1] change in the future. +processingPlugins[1][1].components.insight = insightMarkdownPlugin.renderer; processingPlugins[1][1].components.timeline = timelineMarkdownPlugin.renderer; processingPlugins[1][1].components.osquery = osqueryMarkdownPlugin.renderer; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/index.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/index.tsx new file mode 100644 index 00000000000000..081ab17aa0dff5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/index.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 type { Plugin } from 'unified'; +import React, { useContext, useMemo } from 'react'; +import type { RemarkTokenizer } from '@elastic/eui'; +import { EuiLoadingSpinner, EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useAppToasts } from '../../../../hooks/use_app_toasts'; +import { useInsightQuery } from './use_insight_query'; +import { useInsightDataProviders } from './use_insight_data_providers'; +import { BasicAlertDataContext } from '../../../event_details/investigation_guide_view'; +import { InvestigateInTimelineButton } from '../../../event_details/table/investigate_in_timeline_button'; +import type { AbsoluteTimeRange } from '../../../../store/inputs/model'; + +interface InsightComponentProps { + label?: string; + description?: string; + providers?: string; +} + +export const parser: Plugin = function () { + const Parser = this.Parser; + const tokenizers = Parser.prototype.inlineTokenizers; + const methods = Parser.prototype.inlineMethods; + const insightPrefix = '!{insight'; + + const tokenizeInsight: RemarkTokenizer = function (eat, value, silent) { + if (value.startsWith(insightPrefix) === false) { + return false; + } + + const nextChar = value[insightPrefix.length]; + if (nextChar !== '{' && nextChar !== '}') return false; + if (silent) { + return true; + } + + // is there a configuration? + const hasConfiguration = nextChar === '{'; + + let configuration: InsightComponentProps = {}; + if (hasConfiguration) { + let configurationString = ''; + let openObjects = 0; + + for (let i = insightPrefix.length; i < value.length; i++) { + const char = value[i]; + if (char === '{') { + openObjects++; + configurationString += char; + } else if (char === '}') { + openObjects--; + if (openObjects === -1) { + break; + } + configurationString += char; + } else { + configurationString += char; + } + } + + try { + configuration = JSON.parse(configurationString); + return eat(value)({ + type: 'insight', + ...configuration, + providers: JSON.stringify(configuration.providers), + }); + } catch (err) { + const now = eat.now(); + this.file.fail( + i18n.translate('xpack.securitySolution.markdownEditor.plugins.insightConfigError', { + values: { err }, + defaultMessage: 'Unable to parse insight JSON configuration: {err}', + }), + { + line: now.line, + column: now.column + insightPrefix.length, + } + ); + } + } + return false; + }; + tokenizeInsight.locator = (value: string, fromIndex: number) => { + return value.indexOf(insightPrefix, fromIndex); + }; + tokenizers.insight = tokenizeInsight; + methods.splice(methods.indexOf('text'), 0, 'insight'); +}; + +// receives the configuration from the parser and renders +const InsightComponent = ({ label, description, providers }: InsightComponentProps) => { + const { addError } = useAppToasts(); + let parsedProviders = []; + try { + if (providers !== undefined) { + parsedProviders = JSON.parse(providers); + } + } catch (err) { + addError(err, { + title: i18n.translate('xpack.securitySolution.markdownEditor.plugins.insightProviderError', { + defaultMessage: 'Unable to parse insight provider configuration', + }), + }); + } + const { data: alertData } = useContext(BasicAlertDataContext); + const dataProviders = useInsightDataProviders({ + providers: parsedProviders, + alertData, + }); + const { totalCount, isQueryLoading, oldestTimestamp, hasError } = useInsightQuery({ + dataProviders, + }); + const timerange: AbsoluteTimeRange = useMemo(() => { + return { + kind: 'absolute', + from: oldestTimestamp ?? '', + to: new Date().toISOString(), + }; + }, [oldestTimestamp]); + if (isQueryLoading) { + return ; + } else { + return ( + + + {` ${label} (${totalCount}) - ${description}`} + + ); + } +}; + +export { InsightComponent as renderer }; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.test.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.test.ts new file mode 100644 index 00000000000000..8542c445b5d14a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { renderHook } from '@testing-library/react-hooks'; +import type { DataProvider } from '@kbn/timelines-plugin/common'; +import type { UseInsightDataProvidersProps, Provider } from './use_insight_data_providers'; +import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; +import { useInsightDataProviders } from './use_insight_data_providers'; +import { mockAlertDetailsData } from '../../../event_details/__mocks__'; + +const mockAlertDetailsDataWithIsObject = mockAlertDetailsData.map((detail) => { + return { + ...detail, + isObjectArray: false, + }; +}) as TimelineEventsDetailsItem[]; + +const nestedAndProvider = [ + [ + { + field: 'event.id', + value: 'kibana.alert.rule.uuid', + type: 'parameter', + }, + ], + [ + { + field: 'event.category', + value: 'network', + type: 'literal', + }, + { + field: 'process.pid', + value: 'process.pid', + type: 'parameter', + }, + ], +] as Provider[][]; + +const topLevelOnly = [ + [ + { + field: 'event.id', + value: 'kibana.alert.rule.uuid', + type: 'parameter', + }, + ], + [ + { + field: 'event.category', + value: 'network', + type: 'literal', + }, + ], + [ + { + field: 'process.pid', + value: 'process.pid', + type: 'parameter', + }, + ], +] as Provider[][]; + +const nonExistantField = [ + [ + { + field: 'event.id', + value: 'kibana.alert.rule.parameters.threshold.field', + type: 'parameter', + }, + ], +] as Provider[][]; + +describe('useInsightDataProviders', () => { + it('should return 2 data providers, 1 with a nested provider ANDed to it', () => { + const { result } = renderHook(() => + useInsightDataProviders({ + providers: nestedAndProvider, + alertData: mockAlertDetailsDataWithIsObject, + }) + ); + const providers = result.current; + const providersWithNonEmptyAnd = providers.filter((provider) => provider.and.length > 0); + expect(providers.length).toBe(2); + expect(providersWithNonEmptyAnd.length).toBe(1); + }); + + it('should return 3 data providers without any containing nested ANDs', () => { + const { result } = renderHook(() => + useInsightDataProviders({ + providers: topLevelOnly, + alertData: mockAlertDetailsDataWithIsObject, + }) + ); + const providers = result.current; + const providersWithNonEmptyAnd = providers.filter((provider) => provider.and.length > 0); + expect(providers.length).toBe(3); + expect(providersWithNonEmptyAnd.length).toBe(0); + }); + + it('should use a wildcard for a field not present in an alert', () => { + const { result } = renderHook(() => + useInsightDataProviders({ + providers: nonExistantField, + alertData: mockAlertDetailsDataWithIsObject, + }) + ); + const providers = result.current; + const { + queryMatch: { value }, + } = providers[0]; + expect(providers.length).toBe(1); + expect(value).toBe('*'); + }); + + it('should use template data providers when called without alertData', () => { + const { result } = renderHook(() => + useInsightDataProviders({ + providers: nestedAndProvider, + }) + ); + const providers = result.current; + const [first, second] = providers; + const [nestedSecond] = second.and; + expect(second.type).toBe('default'); + expect(first.type).toBe('template'); + expect(nestedSecond.type).toBe('template'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.ts new file mode 100644 index 00000000000000..5c5de496b04b5b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.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 { useMemo } from 'react'; +import type { QueryOperator, DataProvider } from '@kbn/timelines-plugin/common'; +import { DataProviderType } from '@kbn/timelines-plugin/common'; +import { IS_OPERATOR } from '../../../../../timelines/components/timeline/data_providers/data_provider'; +import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; + +export interface Provider { + field: string; + value: string; + type: 'parameter' | 'value'; +} +export interface UseInsightDataProvidersProps { + providers: Provider[][]; + alertData?: TimelineEventsDetailsItem[] | null; +} + +export const useInsightDataProviders = ({ + providers, + alertData, +}: UseInsightDataProvidersProps): DataProvider[] => { + function getFieldValue(fields: TimelineEventsDetailsItem[], fieldToFind: string) { + const alertField = fields.find((dataField) => dataField.field === fieldToFind); + return alertField?.values ? alertField.values[0] : '*'; + } + const dataProviders: DataProvider[] = useMemo(() => { + if (alertData) { + return providers.map((innerProvider) => { + return innerProvider.reduce((prev, next, index): DataProvider => { + const { field, value, type } = next; + if (index === 0) { + return { + and: [], + enabled: true, + id: JSON.stringify(field + value + type), + name: field, + excluded: false, + kqlQuery: '', + type: DataProviderType.default, + queryMatch: { + field, + value: type === 'parameter' ? getFieldValue(alertData, value) : value, + operator: IS_OPERATOR as QueryOperator, + }, + }; + } else { + const newProvider = { + and: [], + enabled: true, + id: JSON.stringify(field + value + type), + name: field, + excluded: false, + kqlQuery: '', + type: DataProviderType.default, + queryMatch: { + field, + value: type === 'parameter' ? getFieldValue(alertData, value) : value, + operator: IS_OPERATOR as QueryOperator, + }, + }; + prev.and.push(newProvider); + } + return prev; + }, {} as DataProvider); + }); + } else { + return providers.map((innerProvider) => { + return innerProvider.reduce((prev, next, index) => { + const { field, value, type } = next; + if (index === 0) { + return { + and: [], + enabled: true, + id: JSON.stringify(field + value + type), + name: field, + excluded: false, + kqlQuery: '', + type: type === 'parameter' ? DataProviderType.template : DataProviderType.default, + queryMatch: { + field, + value: type === 'parameter' ? `{${value}}` : value, + operator: IS_OPERATOR as QueryOperator, + }, + }; + } else { + const newProvider = { + and: [], + enabled: true, + id: JSON.stringify(field + value + type), + name: field, + excluded: false, + kqlQuery: '', + type: type === 'parameter' ? DataProviderType.template : DataProviderType.default, + queryMatch: { + field, + value: type === 'parameter' ? `{${value}}` : value, + operator: IS_OPERATOR as QueryOperator, + }, + }; + prev.and.push(newProvider); + } + return prev; + }, {} as DataProvider); + }); + } + }, [alertData, providers]); + return dataProviders; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.ts new file mode 100644 index 00000000000000..74942f0f4ad381 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.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 { renderHook } from '@testing-library/react-hooks'; +import type { QueryOperator } from '@kbn/timelines-plugin/common'; +import { DataProviderType } from '@kbn/timelines-plugin/common'; +import { useInsightQuery } from './use_insight_query'; +import { TestProviders } from '../../../../mock'; +import type { UseInsightQuery, UseInsightQueryResult } from './use_insight_query'; +import { IS_OPERATOR } from '../../../../../timelines/components/timeline/data_providers/data_provider'; + +const mockProvider = { + and: [], + enabled: true, + id: 'made-up-id', + name: 'test', + excluded: false, + kqlQuery: '', + type: DataProviderType.default, + queryMatch: { + field: 'event.id', + value: '*', + operator: IS_OPERATOR as QueryOperator, + }, +}; + +describe('useInsightQuery', () => { + it('should return renderable defaults', () => { + const { result } = renderHook( + () => + useInsightQuery({ + dataProviders: [mockProvider], + }), + { + wrapper: TestProviders, + } + ); + const { isQueryLoading, totalCount, oldestTimestamp } = result.current; + expect(isQueryLoading).toBeFalsy(); + expect(totalCount).toBe(-1); + expect(oldestTimestamp).toBe(undefined); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.ts new file mode 100644 index 00000000000000..e7836cd6cd3ad8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.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 { useMemo, useState } from 'react'; +import { getEsQueryConfig } from '@kbn/data-plugin/common'; +import type { DataProvider } from '@kbn/timelines-plugin/common'; +import { TimelineId } from '../../../../../../common/types/timeline'; +import { useKibana } from '../../../../lib/kibana'; +import { combineQueries } from '../../../../lib/kuery'; +import { useTimelineEvents } from '../../../../../timelines/containers'; +import { useSourcererDataView } from '../../../../containers/sourcerer'; +import { SourcererScopeName } from '../../../../store/sourcerer/model'; + +export interface UseInsightQuery { + dataProviders: DataProvider[]; +} + +export interface UseInsightQueryResult { + isQueryLoading: boolean; + totalCount: number; + oldestTimestamp: string | null | undefined; + hasError: boolean; +} + +export const useInsightQuery = ({ dataProviders }: UseInsightQuery): UseInsightQueryResult => { + const { uiSettings } = useKibana().services; + const esQueryConfig = useMemo(() => getEsQueryConfig(uiSettings), [uiSettings]); + const { browserFields, selectedPatterns, indexPattern, dataViewId } = useSourcererDataView( + SourcererScopeName.timeline + ); + const [hasError, setHasError] = useState(false); + const combinedQueries = useMemo(() => { + try { + if (hasError === false) { + const parsedCombinedQueries = combineQueries({ + config: esQueryConfig, + dataProviders, + indexPattern, + browserFields, + filters: [], + kqlQuery: { + query: '', + language: 'kuery', + }, + kqlMode: 'filter', + }); + return parsedCombinedQueries; + } + } catch (err) { + setHasError(true); + return null; + } + }, [browserFields, dataProviders, esQueryConfig, hasError, indexPattern]); + + const [isQueryLoading, { events, totalCount }] = useTimelineEvents({ + dataViewId, + fields: ['*'], + filterQuery: combinedQueries?.filterQuery, + id: TimelineId.active, + indexNames: selectedPatterns, + language: 'kuery', + limit: 1, + runtimeMappings: {}, + }); + const [oldestEvent] = events; + const timestamp = + oldestEvent && oldestEvent.data && oldestEvent.data.find((d) => d.field === '@timestamp'); + const oldestTimestamp = timestamp && timestamp.value && timestamp.value[0]; + return { + isQueryLoading, + totalCount, + oldestTimestamp, + hasError, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx index 3d046e349de311..d65405cd1c48f9 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx @@ -258,6 +258,7 @@ const RunOsqueryButtonRenderer = ({ label?: string; query: string; ecs_mapping: { [key: string]: {} }; + test: []; }; }) => { const [showFlyout, setShowFlyout] = useState(false); diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.tsx index 6dcb93321056ed..449e2adee8bf8a 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/renderer.tsx @@ -24,7 +24,6 @@ const MarkdownRendererComponent: React.FC = ({ children, disableLinks }) () => (props) => , [disableLinks] ); - // Deep clone of the processing plugins to prevent affecting the markdown editor. const processingPluginList = cloneDeep(processingPlugins); // This line of code is TS-compatible and it will break if [1][1] change in the future. diff --git a/x-pack/plugins/security_solution/public/common/containers/source/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/containers/source/__snapshots__/index.test.tsx.snap index 37e6a9b6ba0a63..26b23380f15cc0 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/containers/source/__snapshots__/index.test.tsx.snap @@ -639,5 +639,25 @@ Array [ }, "type": "string", }, + Object { + "aggregatable": false, + "category": "nestedField", + "description": "", + "example": "", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "nestedField.thirdAttributes", + "searchable": true, + "subType": Object { + "nested": Object { + "path": "nestedField", + }, + }, + "type": "date", + }, ] `; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts index 956275d43bac7b..bd29838952bad6 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts +++ b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts @@ -439,6 +439,22 @@ export const mocksSource = { }, }, }, + { + aggregatable: false, + category: 'nestedField', + description: '', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'nestedField.thirdAttributes', + searchable: true, + type: 'date', + subType: { + nested: { + path: 'nestedField', + }, + }, + }, ], }; @@ -952,6 +968,22 @@ export const mockBrowserFields: BrowserFields = { }, }, }, + 'nestedField.thirdAttributes': { + aggregatable: false, + category: 'nestedField', + description: '', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'nestedField.thirdAttributes', + searchable: true, + type: 'date', + subType: { + nested: { + path: 'nestedField', + }, + }, + }, }, }, }; diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/add_to_timeline.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/add_to_timeline.tsx index b654b01dbb4efb..60a4e73a6c5cf5 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/add_to_timeline.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/add_to_timeline.tsx @@ -25,9 +25,11 @@ import { addProvider } from '../../../timelines/store/timeline/actions'; export const getAddToTimelineCellAction = ({ data, pageSize, + closeCellPopover, }: { data?: TimelineNonEcsData[][]; pageSize: number; + closeCellPopover?: () => void; }) => data && data.length > 0 ? function AddToTimeline({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) { @@ -97,6 +99,9 @@ export const getAddToTimelineCellAction = ({ providers: dataProvider, }) ); + if (closeCellPopover) { + closeCellPopover(); + } }, [dataProvider, dispatch]); const addToTimelineProps = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/copy.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/copy.tsx index f4fdb22aff136c..1ddb1c6ea759e6 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/copy.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/copy.tsx @@ -16,9 +16,11 @@ import { EmptyComponent, useKibanaServices } from './helpers'; export const getCopyCellAction = ({ data, pageSize, + closeCellPopover, }: { data?: TimelineNonEcsData[][]; pageSize: number; + closeCellPopover?: () => void; }) => data && data.length > 0 ? function CopyButton({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) { @@ -45,6 +47,7 @@ export const getCopyCellAction = ({ ownFocus: false, showTooltip: false, value, + onClick: closeCellPopover, }; }, [Component, columnId, value]); diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/expanded_cell_value_actions.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/expanded_cell_value_actions.tsx index 0967c9c2533d3b..d3b23bb1a04f4f 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/expanded_cell_value_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/expanded_cell_value_actions.tsx @@ -22,6 +22,7 @@ interface Props { scopeId: string; value: string[] | undefined; onFilterAdded?: () => void; + closeCellPopover?: () => void; } const StyledFlexGroup = styled(EuiFlexGroup)` @@ -40,6 +41,7 @@ const ExpandedCellValueActionsComponent: React.FC = ({ onFilterAdded, scopeId, value, + closeCellPopover, }) => { const { timelines, @@ -99,6 +101,7 @@ const ExpandedCellValueActionsComponent: React.FC = ({ size: 's', showTooltip: false, value, + onClick: closeCellPopover, })} @@ -111,6 +114,7 @@ const ExpandedCellValueActionsComponent: React.FC = ({ size: 's', showTooltip: false, value, + onClick: closeCellPopover, })} diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/filter_for.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/filter_for.tsx index cc721b4944244b..7e0e08557de539 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/filter_for.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/filter_for.tsx @@ -16,9 +16,11 @@ import { EmptyComponent, onFilterAdded, useKibanaServices } from './helpers'; export const getFilterForCellAction = ({ data, pageSize, + closeCellPopover, }: { data?: TimelineNonEcsData[][]; pageSize: number; + closeCellPopover?: () => void; }) => data && data.length > 0 ? function FilterFor({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) { @@ -47,6 +49,7 @@ export const getFilterForCellAction = ({ ownFocus: false, showTooltip: false, value, + onClick: closeCellPopover, }; }, [Component, columnId, filterManager, value]); diff --git a/x-pack/plugins/security_solution/public/common/lib/cell_actions/filter_out.tsx b/x-pack/plugins/security_solution/public/common/lib/cell_actions/filter_out.tsx index 10f36a14e2c5e9..442c7320e83b20 100644 --- a/x-pack/plugins/security_solution/public/common/lib/cell_actions/filter_out.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/cell_actions/filter_out.tsx @@ -15,9 +15,11 @@ import { EmptyComponent, onFilterAdded, useKibanaServices } from './helpers'; export const getFilterOutCellAction = ({ data, pageSize, + closeCellPopover, }: { data?: TimelineNonEcsData[][]; pageSize: number; + closeCellPopover?: () => void; }) => data && data.length > 0 ? function FilterOut({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) { @@ -47,6 +49,7 @@ export const getFilterOutCellAction = ({ ownFocus: false, showTooltip: false, value, + onClick: closeCellPopover, }; }, [Component, columnId, filterManager, value]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts index 6686239e053f9b..d01bc59c0bbe32 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts @@ -435,6 +435,9 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep history_window_start: `now-${ruleFields.historyWindowSize}`, } : { + ...(ruleFields.groupByFields.length > 0 + ? { alert_suppression: { group_by: ruleFields.groupByFields } } + : {}), index: ruleFields.index, filters: ruleFields.queryBar?.filters, language: ruleFields.queryBar?.query?.language, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index 0aa6fa9c20875c..38dbf65542254d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -133,6 +133,8 @@ import { ExceptionsViewer } from '../../../rule_exceptions/components/all_except import type { NavTab } from '../../../../common/components/navigation/types'; import { EditRuleSettingButtonLink } from '../../../../detections/pages/detection_engine/rules/details/components/edit_rule_settings_button_link'; import { useStartMlJobs } from '../../../rule_management/logic/use_start_ml_jobs'; +import { useBulkDuplicateExceptionsConfirmation } from '../../../rule_management_ui/components/rules_table/bulk_actions/use_bulk_duplicate_confirmation'; +import { BulkActionDuplicateExceptionsConfirmation } from '../../../rule_management_ui/components/rules_table/bulk_actions/bulk_duplicate_exceptions_confirmation'; /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. @@ -625,6 +627,13 @@ const RuleDetailsPageComponent: React.FC = ({ [containerElement, onSkipFocusBeforeEventsTable, onSkipFocusAfterEventsTable] ); + const { + isBulkDuplicateConfirmationVisible, + showBulkDuplicateConfirmation, + cancelRuleDuplication, + confirmRuleDuplication, + } = useBulkDuplicateExceptionsConfirmation(); + if ( redirectToDetections( isSignalIndexExists, @@ -646,6 +655,13 @@ const RuleDetailsPageComponent: React.FC = ({ <> + {isBulkDuplicateConfirmationVisible && ( + + )} @@ -736,6 +752,7 @@ const RuleDetailsPageComponent: React.FC = ({ rule, hasActionsPrivileges )} + showBulkDuplicateExceptionsConfirmation={showBulkDuplicateConfirmation} /> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index 40a00178c31b88..7c113670d2f3a6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -28,7 +28,10 @@ import { 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 type { + BulkActionEditPayload, + BulkActionDuplicatePayload, +} from '../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { BulkActionType } from '../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import type { @@ -215,13 +218,23 @@ export interface BulkActionResponse { export type QueryOrIds = { query: string; ids?: undefined } | { query?: undefined; ids: string[] }; type PlainBulkAction = { - type: Exclude; + type: Exclude< + BulkActionType, + BulkActionType.edit | BulkActionType.export | BulkActionType.duplicate + >; } & QueryOrIds; + type EditBulkAction = { type: BulkActionType.edit; editPayload: BulkActionEditPayload[]; } & QueryOrIds; -export type BulkAction = PlainBulkAction | EditBulkAction; + +type DuplicateBulkAction = { + type: BulkActionType.duplicate; + duplicatePayload?: BulkActionDuplicatePayload; +} & QueryOrIds; + +export type BulkAction = PlainBulkAction | EditBulkAction | DuplicateBulkAction; export interface PerformBulkActionProps { bulkAction: BulkAction; @@ -245,6 +258,8 @@ export async function performBulkAction({ query: bulkAction.query, ids: bulkAction.ids, edit: bulkAction.type === BulkActionType.edit ? bulkAction.editPayload : undefined, + duplicate: + bulkAction.type === BulkActionType.duplicate ? bulkAction.duplicatePayload : undefined, }; return KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_BULK_ACTION, { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index fa2b7f16ce9f72..d598969acd155e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -30,6 +30,7 @@ import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; import { RuleExecutionSummary } from '../../../../common/detection_engine/rule_monitoring'; import { + AlertSuppression, AlertsIndex, BuildingBlockType, DataViewId, @@ -191,6 +192,7 @@ export const RuleSchema = t.intersection([ uuid: t.string, version: RuleVersion, execution_summary: RuleExecutionSummary, + alert_suppression: AlertSuppression, }), ]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts index ac22095820255e..f28d6b53821e20 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts @@ -167,6 +167,9 @@ export const mockRuleWithEverything = (id: string): Rule => ({ timestamp_override_fallback_disabled: false, note: '# this is some markdown documentation', version: 1, + alert_suppression: { + group_by: ['host.name'], + }, new_terms_fields: ['host.name'], history_window_start: 'now-7d', }); @@ -226,6 +229,7 @@ export const mockDefineStepRule = (): DefineStepRule => ({ newTermsFields: ['host.ip'], historyWindowSize: '7d', shouldLoadQueryDynamically: false, + groupByFields: [], }); export const mockScheduleStepRule = (): ScheduleStepRule => ({ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_duplicate_exceptions_confirmation.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_duplicate_exceptions_confirmation.tsx new file mode 100644 index 00000000000000..cd8be2d925c3fe --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_duplicate_exceptions_confirmation.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, { useCallback, useState } from 'react'; +import { EuiRadioGroup, EuiText, EuiConfirmModal, EuiSpacer, EuiIconTip } from '@elastic/eui'; +import { DuplicateOptions } from '../../../../../../common/detection_engine/rule_management/constants'; + +import { bulkDuplicateRuleActions as i18n } from './translations'; + +interface BulkDuplicateExceptionsConfirmationProps { + onCancel: () => void; + onConfirm: (s: string) => void; + rulesCount: number; +} + +const BulkActionDuplicateExceptionsConfirmationComponent = ({ + onCancel, + onConfirm, + rulesCount, +}: BulkDuplicateExceptionsConfirmationProps) => { + const [selectedDuplicateOption, setSelectedDuplicateOption] = useState( + DuplicateOptions.withExceptions + ); + + const handleRadioChange = useCallback( + (optionId) => { + setSelectedDuplicateOption(optionId); + }, + [setSelectedDuplicateOption] + ); + + const handleConfirm = useCallback(() => { + onConfirm(selectedDuplicateOption); + }, [onConfirm, selectedDuplicateOption]); + + return ( + + + {i18n.MODAL_TEXT(rulesCount)}{' '} + + + + + + + ); +}; + +export const BulkActionDuplicateExceptionsConfirmation = React.memo( + BulkActionDuplicateExceptionsConfirmationComponent +); + +BulkActionDuplicateExceptionsConfirmation.displayName = 'BulkActionDuplicateExceptionsConfirmation'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/translations.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/translations.tsx index cdeec1aeb4adb6..1b28952acd7d8b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/translations.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/translations.tsx @@ -134,3 +134,59 @@ export const bulkSetSchedule = { /> ), }; + +export const bulkDuplicateRuleActions = { + MODAL_TITLE: (rulesCount: number): JSX.Element => ( + + ), + + MODAL_TEXT: (rulesCount: number): JSX.Element => ( + + ), + + DUPLICATE_EXCEPTIONS_TEXT: (rulesCount: number) => ( + + ), + + DUPLICATE_WITHOUT_EXCEPTIONS_TEXT: (rulesCount: number) => ( + + ), + + CONTINUE_BUTTON: i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicate.exceptionsConfirmation.continueButton', + { + defaultMessage: 'Duplicate', + } + ), + + CANCEL_BUTTON: i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicate.exceptionsConfirmation.cancelButton', + { + defaultMessage: 'Cancel', + } + ), + + DUPLICATE_TOOLTIP: i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicate.exceptionsConfirmation.tooltip', + { + defaultMessage: + ' If you duplicate exceptions, then the shared exceptions list will be duplicated by reference and the default rule exception will be copied and created as a new one', + } + ), +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/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 index 74c2471b00f7e0..68e7c0030bc2a1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/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 @@ -12,6 +12,7 @@ import type { Toast } from '@kbn/core/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { euiThemeVars } from '@kbn/ui-theme'; import React, { useCallback } from 'react'; +import { DuplicateOptions } from '../../../../../../common/detection_engine/rule_management/constants'; import type { BulkActionEditPayload } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { BulkActionType, @@ -46,6 +47,7 @@ interface UseBulkActionsArgs { result: DryRunResult | undefined, action: BulkActionForConfirmation ) => Promise; + showBulkDuplicateConfirmation: () => Promise; completeBulkEditForm: ( bulkActionEditType: BulkActionEditType ) => Promise; @@ -56,6 +58,7 @@ export const useBulkActions = ({ filterOptions, confirmDeletion, showBulkActionConfirmation, + showBulkDuplicateConfirmation, completeBulkEditForm, executeBulkActionsDryRun, }: UseBulkActionsArgs) => { @@ -125,8 +128,16 @@ export const useBulkActions = ({ startTransaction({ name: BULK_RULE_ACTIONS.DUPLICATE }); closePopover(); + const modalDuplicationConfirmationResult = await showBulkDuplicateConfirmation(); + if (modalDuplicationConfirmationResult === null) { + return; + } await executeBulkAction({ type: BulkActionType.duplicate, + duplicatePayload: { + include_exceptions: + modalDuplicationConfirmationResult === DuplicateOptions.withExceptions, + }, ...(isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }), }); clearRulesSelection(); @@ -461,6 +472,7 @@ export const useBulkActions = ({ filterOptions, completeBulkEditForm, downloadExportedRules, + showBulkDuplicateConfirmation, ] ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_duplicate_confirmation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_duplicate_confirmation.ts new file mode 100644 index 00000000000000..9090eba69b009b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_duplicate_confirmation.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useCallback, useRef } from 'react'; + +import { useBoolState } from '../../../../../common/hooks/use_bool_state'; + +/** + * hook that controls bulk duplicate actions exceptions confirmation modal window and its content + */ +export const useBulkDuplicateExceptionsConfirmation = () => { + const [isBulkDuplicateConfirmationVisible, showModal, hideModal] = useBoolState(); + const confirmationPromiseRef = useRef<(result: string | null) => void>(); + + const onConfirm = useCallback((value: string) => { + confirmationPromiseRef.current?.(value); + }, []); + + const onCancel = useCallback(() => { + confirmationPromiseRef.current?.(null); + }, []); + + const initModal = useCallback(() => { + showModal(); + + return new Promise((resolve) => { + confirmationPromiseRef.current = resolve; + }).finally(() => { + hideModal(); + }); + }, [showModal, hideModal]); + + const showBulkDuplicateConfirmation = useCallback(async () => { + const confirmation = await initModal(); + if (confirmation) { + onConfirm(confirmation); + } else { + onCancel(); + } + + return confirmation; + }, [initModal, onConfirm, onCancel]); + + return { + isBulkDuplicateConfirmationVisible, + showBulkDuplicateConfirmation, + cancelRuleDuplication: onCancel, + confirmRuleDuplication: onConfirm, + }; +}; 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 index 163402ac7bfd7c..fa063dbc982425 100644 --- 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 @@ -36,6 +36,8 @@ 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'; +import { useBulkDuplicateExceptionsConfirmation } from './bulk_actions/use_bulk_duplicate_confirmation'; +import { BulkActionDuplicateExceptionsConfirmation } from './bulk_actions/bulk_duplicate_exceptions_confirmation'; import { useStartMlJobs } from '../../../rule_management/logic/use_start_ml_jobs'; const INITIAL_SORT_FIELD = 'enabled'; @@ -101,6 +103,13 @@ export const RulesTables = React.memo(({ selectedTab }) => { approveBulkActionConfirmation, } = useBulkActionsConfirmation(); + const { + isBulkDuplicateConfirmationVisible, + showBulkDuplicateConfirmation, + cancelRuleDuplication, + confirmRuleDuplication, + } = useBulkDuplicateExceptionsConfirmation(); + const { bulkEditActionType, isBulkEditFlyoutVisible, @@ -115,6 +124,7 @@ export const RulesTables = React.memo(({ selectedTab }) => { filterOptions, confirmDeletion, showBulkActionConfirmation, + showBulkDuplicateConfirmation, completeBulkEditForm, executeBulkActionsDryRun, }); @@ -147,12 +157,14 @@ export const RulesTables = React.memo(({ selectedTab }) => { isLoadingJobs, mlJobs, startMlJobs, + showExceptionsDuplicateConfirmation: showBulkDuplicateConfirmation, }); const monitoringColumns = useMonitoringColumns({ hasCRUDPermissions: hasPermissions, isLoadingJobs, mlJobs, startMlJobs, + showExceptionsDuplicateConfirmation: showBulkDuplicateConfirmation, }); const isSelectAllCalled = useRef(false); @@ -259,6 +271,13 @@ export const RulesTables = React.memo(({ selectedTab }) => { onConfirm={approveBulkActionConfirmation} /> )} + {isBulkDuplicateConfirmationVisible && ( + + )} {isBulkEditFlyoutVisible && bulkEditActionType !== undefined && ( Promise; } +interface ActionColumnsProps { + showExceptionsDuplicateConfirmation: () => Promise; +} + const useEnabledColumn = ({ hasCRUDPermissions, startMlJobs }: ColumnsProps): TableColumn => { const hasMlPermissions = useHasMlPermissions(); const hasActionsPrivileges = useHasActionsPrivileges(); @@ -209,19 +213,24 @@ const INTEGRATIONS_COLUMN: TableColumn = { truncateText: true, }; -const useActionsColumn = (): EuiTableActionsColumnType => { - const actions = useRulesTableActions(); +const useActionsColumn = ({ + showExceptionsDuplicateConfirmation, +}: ActionColumnsProps): EuiTableActionsColumnType => { + const actions = useRulesTableActions({ showExceptionsDuplicateConfirmation }); return useMemo(() => ({ actions, width: '40px' }), [actions]); }; +export interface UseColumnsProps extends ColumnsProps, ActionColumnsProps {} + export const useRulesColumns = ({ hasCRUDPermissions, isLoadingJobs, mlJobs, startMlJobs, -}: ColumnsProps): TableColumn[] => { - const actionsColumn = useActionsColumn(); + showExceptionsDuplicateConfirmation, +}: UseColumnsProps): TableColumn[] => { + const actionsColumn = useActionsColumn({ showExceptionsDuplicateConfirmation }); const ruleNameColumn = useRuleNameColumn(); const { isInMemorySorting } = useRulesTableContext().state; const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); @@ -338,9 +347,10 @@ export const useMonitoringColumns = ({ isLoadingJobs, mlJobs, startMlJobs, -}: ColumnsProps): TableColumn[] => { + showExceptionsDuplicateConfirmation, +}: UseColumnsProps): TableColumn[] => { const docLinks = useKibana().services.docLinks; - const actionsColumn = useActionsColumn(); + const actionsColumn = useActionsColumn({ showExceptionsDuplicateConfirmation }); const ruleNameColumn = useRuleNameColumn(); const { isInMemorySorting } = useRulesTableContext().state; const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING); 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 index 185ca7040fe216..62af07af3ea24c 100644 --- 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 @@ -8,6 +8,7 @@ import type { DefaultItemAction } from '@elastic/eui'; import { EuiToolTip } from '@elastic/eui'; import React from 'react'; +import { DuplicateOptions } from '../../../../../common/detection_engine/rule_management/constants'; import { BulkActionType } 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'; @@ -23,7 +24,11 @@ import { import { useDownloadExportedRules } from '../../../rule_management/logic/bulk_actions/use_download_exported_rules'; import { useHasActionsPrivileges } from './use_has_actions_privileges'; -export const useRulesTableActions = (): Array> => { +export const useRulesTableActions = ({ + showExceptionsDuplicateConfirmation, +}: { + showExceptionsDuplicateConfirmation: () => Promise; +}): Array> => { const { navigateToApp } = useKibana().services.application; const hasActionsPrivileges = useHasActionsPrivileges(); const { startTransaction } = useStartTransaction(); @@ -63,9 +68,17 @@ export const useRulesTableActions = (): Array> => { // TODO extract those handlers to hooks, like useDuplicateRule onClick: async (rule: Rule) => { startTransaction({ name: SINGLE_RULE_ACTIONS.DUPLICATE }); + const modalDuplicationConfirmationResult = await showExceptionsDuplicateConfirmation(); + if (modalDuplicationConfirmationResult === null) { + return; + } const result = await executeBulkAction({ type: BulkActionType.duplicate, ids: [rule.id], + duplicatePayload: { + include_exceptions: + modalDuplicationConfirmationResult === DuplicateOptions.withExceptions, + }, }); const createdRules = result?.attributes.results.created; if (createdRules?.length) { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 7d69b8ee521226..aaa2a7ace106af 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -22,6 +22,10 @@ import { ALERT_RULE_TYPE, ALERT_RULE_NOTE, ALERT_RULE_PARAMETERS, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_TERMS, } from '@kbn/rule-data-utils'; import type { TGridModel } from '@kbn/timelines-plugin/public'; @@ -283,6 +287,10 @@ export const isNewTermsAlert = (ecsData: Ecs): boolean => { ); }; +const isSuppressedAlert = (ecsData: Ecs): boolean => { + return getField(ecsData, ALERT_SUPPRESSION_DOCS_COUNT) != null; +}; + export const buildAlertsKqlFilter = ( key: '_id' | 'signal.group.id' | 'kibana.alert.group.id', alertIds: string[], @@ -528,7 +536,7 @@ const createThresholdTimeline = async ( title: i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.createThresholdTimelineFailureTitle', { - defaultMessage: 'Failed to create theshold alert timeline', + defaultMessage: 'Failed to create threshold alert timeline', } ), }); @@ -695,6 +703,160 @@ const createNewTermsTimeline = async ( } }; +const getSuppressedAlertData = (ecsData: Ecs | Ecs[]) => { + const normalizedEcsData: Ecs = Array.isArray(ecsData) ? ecsData[0] : ecsData; + const from = getField(normalizedEcsData, ALERT_SUPPRESSION_START); + const to = getField(normalizedEcsData, ALERT_SUPPRESSION_END); + const terms: Array<{ field: string; value: string | number }> = getField( + normalizedEcsData, + ALERT_SUPPRESSION_TERMS + ); + const dataProviderPartials = terms.map((term) => { + const fieldId = term.field.replace('.', '-'); + return { + id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-${fieldId}-${term.value}`, + name: fieldId, + enabled: true, + excluded: false, + kqlQuery: '', + queryMatch: { + field: term.field, + value: term.value, + operator: ':' as const, + }, + }; + }); + const dataProvider = { + ...dataProviderPartials[0], + and: dataProviderPartials.slice(1), + }; + return { + from, + to, + dataProviders: [dataProvider], + }; +}; + +const createSuppressedTimeline = async ( + ecsData: Ecs, + createTimeline: ({ from, timeline, to }: CreateTimelineProps) => void, + noteContent: string, + templateValues: { + filters?: Filter[]; + query?: string; + dataProviders?: DataProvider[]; + columns?: TGridModel['columns']; + }, + getExceptionFilter: GetExceptionFilter +) => { + try { + const alertResponse = await KibanaServices.get().http.fetch< + estypes.SearchResponse<{ '@timestamp': string; [key: string]: unknown }> + >(DETECTION_ENGINE_QUERY_SIGNALS_URL, { + method: 'POST', + body: JSON.stringify(buildAlertsQuery([ecsData._id])), + }); + const formattedAlertData = + alertResponse?.hits.hits.reduce((acc, { _id, _index, _source = {} }) => { + return [ + ...acc, + { + ...formatAlertToEcsSignal(_source), + _id, + _index, + timestamp: _source['@timestamp'], + }, + ]; + }, []) ?? []; + + const alertDoc = formattedAlertData[0]; + const params = getField(alertDoc, ALERT_RULE_PARAMETERS); + const filters: Filter[] = + (params as MightHaveFilters).filters ?? + (alertDoc.signal?.rule as MightHaveFilters)?.filters ?? + []; + // https://github.com/elastic/kibana/issues/126574 - if the provided filter has no `meta` field + // we expect an empty object to be inserted before calling `createTimeline` + const augmentedFilters = filters.map((filter) => { + return filter.meta != null ? filter : { ...filter, meta: {} }; + }); + const language = params.language ?? alertDoc.signal?.rule?.language ?? 'kuery'; + const query = params.query ?? alertDoc.signal?.rule?.query ?? ''; + const indexNames = getField(alertDoc, ALERT_RULE_INDICES) ?? alertDoc.signal?.rule?.index ?? []; + + const { from, to, dataProviders } = getSuppressedAlertData(alertDoc); + const exceptionsFilter = await getExceptionFilter(ecsData); + + const allFilters = (templateValues.filters ?? augmentedFilters).concat( + !exceptionsFilter ? [] : [exceptionsFilter] + ); + + return createTimeline({ + from, + notes: null, + timeline: { + ...timelineDefaults, + columns: templateValues.columns ?? timelineDefaults.columns, + description: `_id: ${alertDoc._id}`, + filters: allFilters, + dataProviders: templateValues.dataProviders ?? dataProviders, + id: TimelineId.active, + indexNames, + dateRange: { + start: from, + end: to, + }, + eventType: 'all', + kqlQuery: { + filterQuery: { + kuery: { + kind: language, + expression: templateValues.query ?? query, + }, + serializedQuery: templateValues.query ?? query, + }, + }, + }, + to, + ruleNote: noteContent, + }); + } catch (error) { + const { toasts } = KibanaServices.get().notifications; + toasts.addError(error, { + toastMessage: i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.createSuppressedTimelineFailure', + { + defaultMessage: 'Failed to create timeline for document _id: {id}', + values: { id: ecsData._id }, + } + ), + title: i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.createSuppressedTimelineFailureTitle', + { + defaultMessage: 'Failed to create suppressed alert timeline', + } + ), + }); + const from = DEFAULT_FROM_MOMENT.toISOString(); + const to = DEFAULT_TO_MOMENT.toISOString(); + return createTimeline({ + from, + notes: null, + timeline: { + ...timelineDefaults, + id: TimelineId.active, + indexNames: [], + dateRange: { + start: from, + end: to, + }, + eventType: 'all', + }, + to, + }); + } +}; + export const sendBulkEventsToTimelineAction = ( createTimeline: CreateTimeline, ecs: Ecs[], @@ -835,6 +997,19 @@ export const sendAlertToTimelineAction = async ({ }, getExceptionFilter ); + } else if (isSuppressedAlert(ecsData)) { + return createSuppressedTimeline( + ecsData, + createTimeline, + noteContent, + { + filters, + query, + dataProviders, + columns: timeline.columns, + }, + getExceptionFilter + ); } else { return createTimeline({ from, @@ -891,6 +1066,8 @@ export const sendAlertToTimelineAction = async ({ return createThresholdTimeline(ecsData, createTimeline, noteContent, {}, getExceptionFilter); } else if (isNewTermsAlert(ecsData)) { return createNewTermsTimeline(ecsData, createTimeline, noteContent, {}, getExceptionFilter); + } else if (isSuppressedAlert(ecsData)) { + return createSuppressedTimeline(ecsData, createTimeline, noteContent, {}, getExceptionFilter); } else { let { dataProviders, filters } = buildTimelineDataProviderOrFilter( [ecsData._id], diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index b35e1d9b0f89c6..fcaad7017ca478 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -183,6 +183,7 @@ export const requiredFieldsForActions = [ 'kibana.alert.rule.to', 'kibana.alert.rule.uuid', 'kibana.alert.rule.type', + 'kibana.alert.suppression.docs_count', 'kibana.alert.original_event.kind', 'kibana.alert.original_event.module', // Endpoint exception fields 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 cf9711c58f8cb9..90405c022799dd 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 @@ -37,6 +37,7 @@ import type { RequiredFieldArray, Threshold, } from '../../../../../common/detection_engine/rule_schema'; +import { minimumLicenseForSuppression } from '../../../../../common/detection_engine/rule_schema'; import * as i18n from './translations'; import type { BuildQueryBarDescription, BuildThreatDescription, ListItems } from './types'; @@ -47,6 +48,7 @@ import type { } from '../../../pages/detection_engine/rules/types'; import { defaultToEmptyTag } from '../../../../common/components/empty_value'; import { ThreatEuiFlexGroup } from './threat_description'; +import type { LicenseService } from '../../../../../common/license'; const NoteDescriptionContainer = styled(EuiFlexItem)` height: 105px; @@ -512,3 +514,48 @@ export const buildRequiredFieldsDescription = ( }, ]; }; + +export const buildAlertSuppressionDescription = ( + label: string, + values: string[], + license: LicenseService +): ListItems[] => { + if (isEmpty(values)) { + return []; + } + const description = ( + + {values.map((val: string) => + isEmpty(val) ? null : ( + + + {val} + + + ) + )} + + ); + if (license.isAtLeast(minimumLicenseForSuppression)) { + return [ + { + title: label, + description, + }, + ]; + } else { + return [ + { + title: ( + <> + {label}  + + + + + ), + description, + }, + ]; + } +}; 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 aeba71acb6ab88..a2206c9c562e73 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 @@ -29,6 +29,7 @@ import * as i18n from './translations'; import { schema } from '../step_about_rule/schema'; import type { ListItems } from './types'; import type { AboutStepRule } from '../../../pages/detection_engine/rules/types'; +import { createLicenseServiceMock } from '../../../../../common/license/mocks'; jest.mock('../../../../common/lib/kibana'); @@ -44,6 +45,7 @@ describe('description_step', () => { }; let mockFilterManager: FilterManager; let mockAboutStep: AboutStepRule; + const mockLicenseService = createLicenseServiceMock(); beforeEach(() => { setupMock.uiSettings.get.mockImplementation(uiSettingsMock(true)); @@ -253,7 +255,12 @@ describe('description_step', () => { describe('buildListItems', () => { test('returns expected ListItems array when given valid inputs', () => { - const result: ListItems[] = buildListItems(mockAboutStep, schema, mockFilterManager); + const result: ListItems[] = buildListItems( + mockAboutStep, + schema, + mockFilterManager, + mockLicenseService + ); expect(result.length).toEqual(11); }); @@ -265,7 +272,8 @@ describe('description_step', () => { 'tags', 'Tags label', mockAboutStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Tags label'); @@ -277,7 +285,8 @@ describe('description_step', () => { 'description', 'Description label', mockAboutStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Description label'); @@ -289,7 +298,8 @@ describe('description_step', () => { 'jibberjabber', 'JibberJabber label', mockAboutStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result.length).toEqual(0); @@ -311,7 +321,8 @@ describe('description_step', () => { 'queryBar', 'Query bar label', mockQueryBar, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual(<>{i18n.QUERY_LABEL}); @@ -327,7 +338,8 @@ describe('description_step', () => { 'threat', 'Threat label', mockAboutStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Threat label'); @@ -359,7 +371,8 @@ describe('description_step', () => { 'threat', 'Threat label', mockStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result.length).toEqual(0); @@ -378,7 +391,8 @@ describe('description_step', () => { 'threshold', 'Threshold label', mockThreshold, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Threshold label'); @@ -399,7 +413,8 @@ describe('description_step', () => { 'threshold', 'Threshold label', mockThreshold, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Threshold label'); @@ -416,7 +431,8 @@ describe('description_step', () => { 'references', 'Reference label', mockAboutStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Reference label'); @@ -430,7 +446,8 @@ describe('description_step', () => { 'falsePositives', 'False positives label', mockAboutStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('False positives label'); @@ -444,7 +461,8 @@ describe('description_step', () => { 'severity', 'Severity label', mockAboutStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Severity'); @@ -458,7 +476,8 @@ describe('description_step', () => { 'riskScore', 'Risk score label', mockAboutStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Risk score'); @@ -473,7 +492,8 @@ describe('description_step', () => { 'timeline', 'Timeline label', mockDefineStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Timeline label'); @@ -491,7 +511,8 @@ describe('description_step', () => { 'timeline', 'Timeline label', mockStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Timeline label'); @@ -505,7 +526,8 @@ describe('description_step', () => { 'note', 'Investigation guide', mockAboutStep, - mockFilterManager + mockFilterManager, + mockLicenseService ); expect(result[0].title).toEqual('Investigation guide'); 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 92879c56e9885d..d22bff896eb0b7 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 @@ -43,12 +43,15 @@ import { buildThreatMappingDescription, buildEqlOptionsDescription, buildRequiredFieldsDescription, + buildAlertSuppressionDescription, } from './helpers'; 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 '../../../../detection_engine/rule_creation_ui/pages/rule_creation/helpers'; +import { useLicense } from '../../../../common/hooks/use_license'; +import type { LicenseService } from '../../../../../common/license'; const DescriptionListContainer = styled(EuiDescriptionList)` &.euiDescriptionList--column .euiDescriptionList__title { @@ -74,6 +77,7 @@ export const StepRuleDescriptionComponent = ({ schema, }: StepRuleDescriptionProps) => { const kibana = useKibana(); + const license = useLicense(); const [filterManager] = useState(new FilterManager(kibana.services.uiSettings)); const keys = Object.keys(schema); @@ -96,7 +100,10 @@ export const StepRuleDescriptionComponent = ({ return [...acc, buildActionsDescription(get(key, data), get([key, 'label'], schema))]; } - return [...acc, ...buildListItems(data, pick(key, schema), filterManager, indexPatterns)]; + return [ + ...acc, + ...buildListItems(data, pick(key, schema), filterManager, license, indexPatterns), + ]; }, []); if (columns === 'multi') { @@ -137,6 +144,7 @@ export const buildListItems = ( data: unknown, schema: FormSchema, filterManager: FilterManager, + license: LicenseService, indexPatterns?: DataViewBase ): ListItems[] => Object.keys(schema).reduce( @@ -147,6 +155,7 @@ export const buildListItems = ( get([field, 'label'], schema), data, filterManager, + license, indexPatterns ), ], @@ -170,6 +179,7 @@ export const getDescriptionItem = ( label: string, data: unknown, filterManager: FilterManager, + license: LicenseService, indexPatterns?: DataViewBase ): ListItems[] => { if (field === 'queryBar') { @@ -186,6 +196,9 @@ export const getDescriptionItem = ( savedQueryName, indexPatterns, }); + } else if (field === 'groupByFields') { + const values: string[] = get(field, data); + return buildAlertSuppressionDescription(label, values, license); } else if (field === 'eqlOptions') { const eqlOptions: EqlOptionsSelected = get(field, data); return buildEqlOptionsDescription(eqlOptions); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/translations.tsx index acd2040ad3e273..ed7f761bec1435 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/translations.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/translations.tsx @@ -146,3 +146,11 @@ export const EQL_TIMESTAMP_FIELD_LABEL = i18n.translate( defaultMessage: 'Timestamp field', } ); + +export const ALERT_SUPPRESSION_INSUFFICIENT_LICENSE = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDescription.alertSuppressionInsufficientLicense', + { + defaultMessage: + 'Alert suppression is configured but will not be applied due to insufficient licensing', + } +); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/index.tsx new file mode 100644 index 00000000000000..579167c73f4cba --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/index.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; + +import { EuiToolTip } from '@elastic/eui'; +import type { DataViewFieldBase } from '@kbn/es-query'; +import type { FieldHook } from '../../../../shared_imports'; +import { Field } from '../../../../shared_imports'; +import { GROUP_BY_FIELD_PLACEHOLDER, GROUP_BY_FIELD_LICENSE_WARNING } from './translations'; + +interface GroupByFieldsProps { + browserFields: DataViewFieldBase[]; + isDisabled: boolean; + field: FieldHook; +} + +const FIELD_COMBO_BOX_WIDTH = 410; + +const fieldDescribedByIds = 'detectionEngineStepDefineRuleGroupByField'; + +export const GroupByComponent: React.FC = ({ + browserFields, + isDisabled, + field, +}: GroupByFieldsProps) => { + const fieldEuiFieldProps = useMemo( + () => ({ + fullWidth: true, + noSuggestions: false, + options: browserFields.map((browserField) => ({ label: browserField.name })), + placeholder: GROUP_BY_FIELD_PLACEHOLDER, + onCreateOption: undefined, + style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, + isDisabled, + }), + [browserFields, isDisabled] + ); + const fieldComponent = ( + + ); + return isDisabled ? ( + + {fieldComponent} + + ) : ( + fieldComponent + ); +}; + +export const GroupByFields = React.memo(GroupByComponent); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/translations.ts new file mode 100644 index 00000000000000..d0df6a73200159 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/translations.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 { i18n } from '@kbn/i18n'; + +export const GROUP_BY_FIELD_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupBy.placeholderText', + { + defaultMessage: 'Select a field', + } +); + +export const GROUP_BY_FIELD_LICENSE_WARNING = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupBy.licenseWarning', + { + defaultMessage: 'Alert suppression is enabled with Platinum license or above', + } +); 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 816c2963779f92..0276d89ae14491 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 @@ -15,6 +15,8 @@ import { RuleActionsOverflow } from '.'; import { mockRule } from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock'; import { TestProviders } from '../../../../common/mock'; +const showBulkDuplicateExceptionsConfirmation = () => Promise.resolve(null); + jest.mock( '../../../../detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action' ); @@ -50,6 +52,7 @@ describe('RuleActionsOverflow', () => { test('menu items rendered when a rule is passed to the component', () => { const { getByTestId } = render( { test('menu is empty when no rule is passed to the component', () => { const { getByTestId } = render( - , + , { wrapper: TestProviders } ); fireEvent.click(getByTestId('rules-details-popover-button-icon')); @@ -76,6 +84,7 @@ describe('RuleActionsOverflow', () => { test('it does not open the popover when rules-details-popover-button-icon is clicked when the user does not have permission', () => { const { getByTestId } = render( { test('it closes the popover when rules-details-duplicate-rule is clicked', () => { const { getByTestId } = render( { expect(getByTestId('rules-details-popover')).not.toHaveTextContent(/.+/); }); - - test('it calls duplicate action when rules-details-duplicate-rule is clicked', () => { - const executeBulkAction = jest.fn(); - useExecuteBulkActionMock.mockReturnValue({ executeBulkAction }); - - const { getByTestId } = render( - , - { wrapper: TestProviders } - ); - fireEvent.click(getByTestId('rules-details-popover-button-icon')); - fireEvent.click(getByTestId('rules-details-duplicate-rule')); - - expect(executeBulkAction).toHaveBeenCalledWith( - expect.objectContaining({ type: 'duplicate' }) - ); - }); - - test('it calls duplicate action with the rule and rule.id when rules-details-duplicate-rule is clicked', () => { - const executeBulkAction = jest.fn(); - useExecuteBulkActionMock.mockReturnValue({ executeBulkAction }); - - const { getByTestId } = render( - , - { wrapper: TestProviders } - ); - fireEvent.click(getByTestId('rules-details-popover-button-icon')); - fireEvent.click(getByTestId('rules-details-duplicate-rule')); - - expect(executeBulkAction).toHaveBeenCalledWith({ type: 'duplicate', ids: ['id'] }); - }); }); describe('rules details export rule', () => { @@ -150,6 +122,7 @@ describe('RuleActionsOverflow', () => { const { getByTestId } = render( { test('it closes the popover when rules-details-export-rule is clicked', () => { const { getByTestId } = render( { test('it closes the popover when rules-details-delete-rule is clicked', () => { const { getByTestId } = render( { const { getByTestId } = render( { const rule = mockRule('id'); const { getByTestId } = render( - , + , { wrapper: TestProviders } ); fireEvent.click(getByTestId('rules-details-popover-button-icon')); 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 1281e67a49b71b..f5345f42f810bc 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 @@ -15,6 +15,7 @@ import { import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { APP_UI_ID, SecurityPageName } from '../../../../../common/constants'; +import { DuplicateOptions } from '../../../../../common/detection_engine/rule_management/constants'; import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; @@ -47,6 +48,7 @@ interface RuleActionsOverflowComponentProps { rule: Rule | null; userHasPermissions: boolean; canDuplicateRuleWithActions: boolean; + showBulkDuplicateExceptionsConfirmation: () => Promise; } /** @@ -56,6 +58,7 @@ const RuleActionsOverflowComponent = ({ rule, userHasPermissions, canDuplicateRuleWithActions, + showBulkDuplicateExceptionsConfirmation, }: RuleActionsOverflowComponentProps) => { const [isPopoverOpen, , closePopover, togglePopover] = useBoolState(); const { navigateToApp } = useKibana().services.application; @@ -83,10 +86,20 @@ const RuleActionsOverflowComponent = ({ onClick={async () => { startTransaction({ name: SINGLE_RULE_ACTIONS.DUPLICATE }); closePopover(); + const modalDuplicationConfirmationResult = + await showBulkDuplicateExceptionsConfirmation(); + if (modalDuplicationConfirmationResult === null) { + return; + } const result = await executeBulkAction({ type: BulkActionType.duplicate, ids: [rule.id], + duplicatePayload: { + include_exceptions: + modalDuplicationConfirmationResult === DuplicateOptions.withExceptions, + }, }); + const createdRules = result?.attributes.results.created; if (createdRules?.length) { goToRuleEditPage(createdRules[0].id, navigateToApp); @@ -148,6 +161,7 @@ const RuleActionsOverflowComponent = ({ navigateToApp, onRuleDeletedCallback, rule, + showBulkDuplicateExceptionsConfirmation, startTransaction, userHasPermissions, downloadExportedRules, 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 5c5554b72ad8ac..1da78d8d97a29d 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 @@ -56,6 +56,7 @@ export const stepDefineStepMLRule: DefineStepRule = { timeline: { id: null, title: null }, eqlOptions: {}, dataSourceType: DataSourceType.IndexPatterns, + groupByFields: ['host.name'], newTermsFields: ['host.ip'], historyWindowSize: '7d', shouldLoadQueryDynamically: false, 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 9cbe2362acb6dd..82018631712278 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 @@ -78,6 +78,9 @@ import { ScheduleItem } from '../schedule_item_form'; import { DocLink } from '../../../../common/components/links_to_docs/doc_link'; import { defaultCustomQuery } from '../../../pages/detection_engine/rules/utils'; import { getIsRulePreviewDisabled } from '../rule_preview/helpers'; +import { GroupByFields } from '../group_by_fields'; +import { useLicense } from '../../../../common/hooks/use_license'; +import { minimumLicenseForSuppression } from '../../../../../common/detection_engine/rule_schema'; const CommonUseField = getUseField({ component: Field }); @@ -134,6 +137,7 @@ const StepDefineRuleComponent: FC = ({ const [indexModified, setIndexModified] = useState(false); const [threatIndexModified, setThreatIndexModified] = useState(false); const [dataViewTitle, setDataViewTitle] = useState(); + const license = useLicense(); const { form } = useForm({ defaultValue: initialState, @@ -771,6 +775,19 @@ const StepDefineRuleComponent: FC = ({ )} + + + + <> = { index: { @@ -557,6 +559,44 @@ export const schema: FormSchema = { }, ], }, + groupByFields: { + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabel', + { + defaultMessage: 'Suppress Alerts By', + } + ), + labelAppend: OptionalFieldLabel, + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldGroupByFieldHelpText', + { + defaultMessage: 'Select field(s) to use for suppressing extra alerts', + } + ), + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ formData }] = args; + const needsValidation = isQueryRule(formData.ruleType); + if (!needsValidation) { + return; + } + return fieldValidators.maxLengthField({ + length: 3, + message: i18n.translate( + 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.groupByFieldsMax', + { + defaultMessage: 'Number of grouping fields must be at most 3', + } + ), + })(...args); + }, + }, + ], + }, newTermsFields: { type: FIELD_TYPES.COMBO_BOX, label: i18n.translate( diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx index f6dfc40caa69ab..e437ad1120c04d 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/render_cell_value.tsx @@ -6,7 +6,10 @@ */ import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import { EuiIcon, EuiToolTip } from '@elastic/eui'; import React, { useMemo } from 'react'; +import styled from 'styled-components'; +import { find } from 'lodash/fp'; import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step'; import { isDetectionsAlertsTable } from '../../../common/components/top_n/helpers'; import { @@ -21,6 +24,12 @@ import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import type { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering'; import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; +import { SUPPRESSED_ALERT_TOOLTIP } from './translations'; + +const SuppressedAlertIconWrapper = styled.div` + display: inline-flex; +`; + /** * This implementation of `EuiDataGrid`'s `renderCellValue` * accepts `EuiDataGridCellValueElementProps`, plus `data` @@ -39,7 +48,9 @@ export const RenderCellValue: React.FC ); + + return columnId === SIGNAL_RULE_NAME_FIELD_NAME && + suppressionCount?.value && + parseInt(suppressionCount.value[0], 10) > 0 ? ( + + + + +   + {component} + + ) : ( + component + ); }; export const useRenderCellValue = ({ diff --git a/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/translations.ts b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/translations.ts new file mode 100644 index 00000000000000..4f34bd2dc03cec --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/configurations/security_solution_detections/translations.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 { i18n } from '@kbn/i18n'; + +export const SUPPRESSED_ALERT_TOOLTIP = (numAlertsSuppressed: number) => + i18n.translate('xpack.securitySolution.configurations.suppressedAlerts', { + defaultMessage: 'Alert has {numAlertsSuppressed} suppressed alerts', + values: { numAlertsSuppressed }, + }); 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 82934d0cccac23..1c7433c9caabdf 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 @@ -115,6 +115,7 @@ describe('rule helpers', () => { eventCategoryField: undefined, tiebreakerField: undefined, }, + groupByFields: ['host.name'], newTermsFields: ['host.name'], historyWindowSize: '7d', }; @@ -258,6 +259,7 @@ describe('rule helpers', () => { eventCategoryField: undefined, tiebreakerField: undefined, }, + groupByFields: [], newTermsFields: [], historyWindowSize: '7d', shouldLoadQueryDynamically: true, @@ -312,6 +314,7 @@ describe('rule helpers', () => { eventCategoryField: undefined, tiebreakerField: undefined, }, + groupByFields: [], newTermsFields: [], historyWindowSize: '7d', shouldLoadQueryDynamically: false, 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 216e202052ee76..0452644b12d001 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 @@ -135,6 +135,7 @@ export const getDefineStepsData = (rule: Rule): DefineStepRule => ({ ? convertHistoryStartToSize(rule.history_window_start) : '7d', shouldLoadQueryDynamically: Boolean(rule.type === 'saved_query' && rule.saved_id), + groupByFields: rule.alert_suppression?.group_by ?? [], }); const convertHistoryStartToSize = (relativeTime: string) => { 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 81ebc4b24cf9c8..680249fb8f89f3 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 @@ -168,6 +168,7 @@ export interface DefineStepRule { newTermsFields: string[]; historyWindowSize: string; shouldLoadQueryDynamically: boolean; + groupByFields: string[]; } export interface ScheduleStepRule { 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 5c04e749e481dc..e7cc2967ba62d2 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 @@ -143,6 +143,7 @@ export const stepDefineDefaultValue: DefineStepRule = { newTermsFields: [], historyWindowSize: '7d', shouldLoadQueryDynamically: false, + groupByFields: [], }; export const stepAboutDefaultValue: AboutStepRule = { diff --git a/x-pack/plugins/security_solution/public/exceptions/api/list_api.ts b/x-pack/plugins/security_solution/public/exceptions/api/list_api.ts index caf656e198da14..ba7a24bb442857 100644 --- a/x-pack/plugins/security_solution/public/exceptions/api/list_api.ts +++ b/x-pack/plugins/security_solution/public/exceptions/api/list_api.ts @@ -10,6 +10,7 @@ import { fetchExceptionLists, updateExceptionList } from '@kbn/securitysolution- import type { HttpSetup } from '@kbn/core-http-browser'; import { getFilters } from '@kbn/securitysolution-list-utils'; import type { List, ListArray } from '@kbn/securitysolution-io-ts-list-types'; +import { asyncForEach } from '@kbn/std'; import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../common/endpoint/service/artifacts/constants'; import type { FetchListById, @@ -80,12 +81,13 @@ export const updateList = async ({ list, http }: UpdateExceptionList) => { export const unlinkListFromRules = async ({ rules, listId }: UnlinkListFromRules) => { try { + if (!rules.length) return; const abortCtrl = new AbortController(); - rules.map((rule) => { + await asyncForEach(rules, async (rule) => { const exceptionLists: ListArray | [] = (rule.exceptions_list ?? []).filter( ({ list_id: id }) => id !== listId ); - return patchRule({ + await patchRule({ ruleProperties: { rule_id: rule.rule_id, exceptions_list: exceptionLists, @@ -106,8 +108,9 @@ export const linkListToRules = async ({ listNamespaceType, }: LinkListToRules) => { try { + if (!rules.length) return; const abortCtrl = new AbortController(); - rules.map((rule) => { + await asyncForEach(rules, async (rule) => { const newExceptionList: List = { list_id: listId, id, @@ -115,7 +118,7 @@ export const linkListToRules = async ({ namespace_type: listNamespaceType, }; const exceptionLists: ListArray | [] = [...(rule.exceptions_list ?? []), newExceptionList]; - return patchRule({ + await patchRule({ ruleProperties: { rule_id: rule.rule_id, exceptions_list: exceptionLists, diff --git a/x-pack/plugins/security_solution/public/exceptions/components/list_with_search/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/list_with_search/index.tsx index a6d7701bffe023..9deda24248fc81 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/list_with_search/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/list_with_search/index.tsx @@ -26,9 +26,14 @@ import { ListExceptionItems } from '..'; interface ListWithSearchComponentProps { list: ExceptionListSchema; isReadOnly: boolean; + refreshExceptions?: boolean; } -const ListWithSearchComponent: FC = ({ list, isReadOnly }) => { +const ListWithSearchComponent: FC = ({ + list, + isReadOnly, + refreshExceptions, +}) => { const { listName, exceptions, @@ -50,7 +55,7 @@ const ListWithSearchComponent: FC = ({ list, isRea onPaginationChange, handleCancelExceptionItemFlyout, handleConfirmExceptionFlyout, - } = useListWithSearchComponent(list); + } = useListWithSearchComponent(list, refreshExceptions); return ( <> {showAddExceptionFlyout ? ( diff --git a/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx index 9aef67fac6280e..f4254123999c19 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx @@ -27,13 +27,21 @@ import * as i18n from '../../translations'; interface ManageRulesProps { linkedRules: Rule[]; showButtonLoader?: boolean; + saveIsDisabled?: boolean; onSave: () => void; onCancel: () => void; onRuleSelectionChange: (rulesSelectedToAdd: Rule[]) => void; } export const ManageRules: FC = memo( - ({ linkedRules, showButtonLoader, onSave, onCancel, onRuleSelectionChange }) => { + ({ + linkedRules, + showButtonLoader, + saveIsDisabled = true, + onSave, + onCancel, + onRuleSelectionChange, + }) => { const complicatedFlyoutTitleId = useGeneratedHtmlId({ prefix: 'complicatedFlyoutTitle', }); @@ -67,7 +75,12 @@ export const ManageRules: FC = memo( - + {i18n.MANAGE_RULES_SAVE} diff --git a/x-pack/plugins/security_solution/public/exceptions/components/shared_list_utilty_bar/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/shared_list_utilty_bar/index.tsx index e4c3653a2693ad..5d1cdf3441a48d 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/shared_list_utilty_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/shared_list_utilty_bar/index.tsx @@ -7,6 +7,10 @@ import React from 'react'; +import type { Sort } from '@kbn/securitysolution-io-ts-list-types'; +import styled from 'styled-components'; +import { EuiContextMenuPanel, EuiContextMenuItem, EuiIcon } from '@elastic/eui'; + import { UtilityBar, UtilityBarAction, @@ -19,12 +23,19 @@ import * as i18n from '../../translations'; interface ExceptionsTableUtilityBarProps { onRefresh?: () => void; totalExceptionLists: number; + sort?: Sort; + setSort?: (s: Sort) => void; + sortFields?: Array<{ field: string; label: string; defaultOrder: 'asc' | 'desc' }>; } export const ExceptionsTableUtilityBar: React.FC = ({ onRefresh, totalExceptionLists, + setSort, + sort, + sortFields, }) => { + const selectedSortField = sortFields?.find((sortField) => sortField.field === sort?.field); return ( @@ -44,8 +55,66 @@ export const ExceptionsTableUtilityBar: React.FC + + <> + + {sort && ( + ( + { + const isSelectedSortItem = selectedSortField?.field === item.field; + let nextSortOrder = item.defaultOrder; + if (isSelectedSortItem) { + nextSortOrder = sort.order === 'asc' ? 'desc' : 'asc'; + } + return ( + + setSort?.({ + field: item.field, + order: nextSortOrder, + }) + } + > + + {item.label}{' '} + {selectedSortField?.field === item.field && ( + + )} + + + ); + })} + /> + )} + > + + {i18n.SORT_BY}{' '} + {sortFields?.find((sortField) => sortField.field === sort.field)?.label} + + + )} + + + ); }; +const SortMenuItem = styled('div')` + display: flex; + align-items: center; +`; + +const SortIcon = styled(EuiIcon)` + margin-left: 8px; +`; + ExceptionsTableUtilityBar.displayName = 'ExceptionsTableUtilityBar'; diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts index c378a54501825f..c885cabda32f06 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts @@ -14,6 +14,7 @@ import { ViewerStatus } from '@kbn/securitysolution-exception-list-components'; import { useParams } from 'react-router-dom'; import type { ExceptionListSchema, NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; import { useApi } from '@kbn/securitysolution-list-hooks'; +import { isEqual } from 'lodash'; import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../common/endpoint/service/artifacts/constants'; import { useUserData } from '../../../detections/components/user_info'; import { APP_UI_ID, SecurityPageName } from '../../../../common/constants'; @@ -71,6 +72,8 @@ export const useExceptionListDetails = () => { const [referenceModalState, setReferenceModalState] = useState( exceptionReferenceModalInitialState ); + const [disableManageButton, setDisableManageButton] = useState(true); + const [refreshExceptions, setRefreshExceptions] = useState(false); const headerBackOptions: BackOptions = useMemo( () => ({ @@ -254,6 +257,13 @@ export const useExceptionListDetails = () => { // #region Manage Rules + const resetManageRulesAfterSaving = useCallback(() => { + setLinkedRules(newLinkedRules); + setNewLinkedRules(newLinkedRules); + setShowManageRulesFlyout(false); + setShowManageButtonLoader(false); + setDisableManageButton(true); + }, [newLinkedRules]); const onManageRules = useCallback(() => { setShowManageRulesFlyout(true); }, []); @@ -268,17 +278,21 @@ export const useExceptionListDetails = () => { const onRuleSelectionChange = useCallback((value) => { setNewLinkedRules(value); + setDisableManageButton(false); }, []); const onSaveManageRules = useCallback(async () => { - setShowManageButtonLoader(true); try { - if (!list) return; + if (!list) return setShowManageRulesFlyout(false); + + setShowManageButtonLoader(true); const rulesToAdd = getRulesToAdd(); const rulesToRemove = getRulesToRemove(); - if (!rulesToAdd.length && !rulesToRemove.length) return; - await Promise.all([ + if ((!rulesToAdd.length && !rulesToRemove.length) || isEqual(rulesToAdd, rulesToRemove)) + return resetManageRulesAfterSaving(); + + Promise.all([ unlinkListFromRules({ rules: rulesToRemove, listId: exceptionListId }), linkListToRules({ rules: rulesToAdd, @@ -287,15 +301,23 @@ export const useExceptionListDetails = () => { listType: list.type, listNamespaceType: list.namespace_type, }), - ]); - setShowManageButtonLoader(false); - setNewLinkedRules([]); - setLinkedRules(newLinkedRules); - setShowManageRulesFlyout(false); + ]) + .then(() => { + setRefreshExceptions(true); + resetManageRulesAfterSaving(); + }) + .then(() => setRefreshExceptions(false)); } catch (err) { handleErrorStatus(err); } - }, [list, getRulesToAdd, getRulesToRemove, exceptionListId, newLinkedRules, handleErrorStatus]); + }, [ + list, + getRulesToAdd, + getRulesToRemove, + exceptionListId, + resetManageRulesAfterSaving, + handleErrorStatus, + ]); const onCancelManageRules = useCallback(() => { setShowManageRulesFlyout(false); }, []); @@ -319,6 +341,8 @@ export const useExceptionListDetails = () => { referenceModalState, showReferenceErrorModal, showManageButtonLoader, + refreshExceptions, + disableManageButton, handleDelete, onEditListDetails, onExportList, diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_exception_items/index.ts b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_exception_items/index.ts index 0da8169baf2f43..ad1db5e82585ce 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_exception_items/index.ts +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_exception_items/index.ts @@ -133,7 +133,6 @@ export const useListExceptionItems = ({ lastUpdated, pagination, exceptionViewerStatus: viewerStatus, - ruleReferences: exceptionListReferences, fetchItems, onDeleteException, diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_with_search/index.ts b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_with_search/index.ts index 1cc5f364d60ef1..14a51deedb4931 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_with_search/index.ts +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_with_search/index.ts @@ -18,10 +18,12 @@ import { ViewerStatus } from '@kbn/securitysolution-exception-list-components'; import * as i18n from '../../translations'; import { useListExceptionItems } from '..'; -export const useListWithSearchComponent = (list: ExceptionListSchema) => { +export const useListWithSearchComponent = ( + list: ExceptionListSchema, + refreshExceptions?: boolean +) => { const [showAddExceptionFlyout, setShowAddExceptionFlyout] = useState(false); const [showEditExceptionFlyout, setShowEditExceptionFlyout] = useState(false); - const [exceptionToEdit, setExceptionToEdit] = useState(); const [viewerStatus, setViewerStatus] = useState(ViewerStatus.LOADING); @@ -54,7 +56,7 @@ export const useListWithSearchComponent = (list: ExceptionListSchema) => { useEffect(() => { fetchItems(null, ViewerStatus.LOADING); - }, [fetchItems]); + }, [fetchItems, refreshExceptions]); const emptyViewerTitle = useMemo(() => { return viewerStatus === ViewerStatus.EMPTY ? i18n.EXCEPTION_LIST_EMPTY_VIEWER_TITLE : ''; diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx index 9fdc2e9890125d..32544e23f5cb34 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx @@ -40,6 +40,8 @@ export const ListsDetailViewComponent: FC = () => { showReferenceErrorModal, referenceModalState, showManageButtonLoader, + refreshExceptions, + disableManageButton, onEditListDetails, onExportList, onManageRules, @@ -76,7 +78,7 @@ export const ListsDetailViewComponent: FC = () => { /> - + { {showManageRulesFlyout ? ( ) : null} diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx index 6cc7120e6df997..9cc6fd15b7ad74 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx @@ -71,6 +71,14 @@ const exceptionReferenceModalInitialState: ReferenceModalState = { listNamespaceType: 'single', }; +const SORT_FIELDS: Array<{ field: string; label: string; defaultOrder: 'asc' | 'desc' }> = [ + { + field: 'created_at', + label: i18n.SORT_BY_CREATE_AT, + defaultOrder: 'desc', + }, +]; + export const SharedLists = React.memo(() => { const [{ loading: userInfoLoading, canUserCRUD, canUserREAD }] = useUserData(); @@ -89,15 +97,23 @@ export const SharedLists = React.memo(() => { const [filters, setFilters] = useState({ types: [ExceptionListTypeEnum.DETECTION, ExceptionListTypeEnum.ENDPOINT], }); - const [loadingExceptions, exceptions, pagination, setPagination, refreshExceptions] = - useExceptionLists({ - errorMessage: i18n.ERROR_EXCEPTION_LISTS, - filterOptions: filters, - http, - namespaceTypes: ['single', 'agnostic'], - notifications, - hideLists: ALL_ENDPOINT_ARTIFACT_LIST_IDS, - }); + + const [ + loadingExceptions, + exceptions, + pagination, + setPagination, + refreshExceptions, + sort, + setSort, + ] = useExceptionLists({ + errorMessage: i18n.ERROR_EXCEPTION_LISTS, + filterOptions: filters, + http, + namespaceTypes: ['single', 'agnostic'], + notifications, + hideLists: ALL_ENDPOINT_ARTIFACT_LIST_IDS, + }); const [loadingTableInfo, exceptionListsWithRuleRefs, exceptionsListsRef] = useAllExceptionLists({ exceptionLists: exceptions ?? [], }); @@ -470,6 +486,9 @@ export const SharedLists = React.memo(() => { {exceptionListsWithRuleRefs.length > 0 && canUserCRUD !== null && canUserREAD !== null && (
diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts b/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts index 4b3c118e16e5bd..45600807749ff4 100644 --- a/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts +++ b/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts @@ -121,13 +121,13 @@ export const MANAGE_RULES_DESCRIPTION = i18n.translate( export const DELETE_EXCEPTION_LIST = i18n.translate( 'xpack.securitySolution.exceptionsTable.deleteExceptionList', { - defaultMessage: 'Delete Exception List', + defaultMessage: 'Delete exception list', } ); export const EXPORT_EXCEPTION_LIST = i18n.translate( 'xpack.securitySolution.exceptionsTable.exportExceptionList', { - defaultMessage: 'Export Exception List', + defaultMessage: 'Export exception list', } ); diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts index e545b782951c53..8e070f24f33532 100644 --- a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts +++ b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts @@ -282,14 +282,14 @@ export const EXCEPTIONS = i18n.translate( export const CREATE_SHARED_LIST_BUTTON = i18n.translate( 'xpack.securitySolution.exceptions.manageExceptions.createSharedListButton', { - defaultMessage: 'create shared list', + defaultMessage: 'Create shared list', } ); export const CREATE_BUTTON_ITEM_BUTTON = i18n.translate( 'xpack.securitySolution.exceptions.manageExceptions.createItemButton', { - defaultMessage: 'create exception item', + defaultMessage: 'Create exception item', } ); @@ -347,3 +347,14 @@ export const SUCCESS_TITLE = i18n.translate( defaultMessage: 'created list', } ); + +export const SORT_BY = i18n.translate('xpack.securitySolution.exceptions.sortBy', { + defaultMessage: 'Sort by:', +}); + +export const SORT_BY_CREATE_AT = i18n.translate( + 'xpack.securitySolution.exceptions.sortByCreateAt', + { + defaultMessage: 'Created At', + } +); diff --git a/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/columns.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/columns.test.tsx new file mode 100644 index 00000000000000..ef908b973dde28 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/columns.test.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { getHostRiskScoreColumns } from './columns'; +import { TestProviders } from '../../../common/mock'; +import type { HostRiskScoreColumns } from '.'; + +describe('getHostRiskScoreColumns', () => { + test('should render host score rounded', () => { + const columns: HostRiskScoreColumns = getHostRiskScoreColumns({ + dispatchSeverityUpdate: jest.fn(), + }); + + const riskScore = 10.11111111; + const riskScoreColumn = columns[1]; + const renderedColumn = riskScoreColumn.render!(riskScore, null); + + const { queryByTestId } = render({renderedColumn}); + + expect(queryByTestId('risk-score-truncate')).toHaveTextContent('10'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/columns.tsx b/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/columns.tsx index b7de3eba5ebe64..0fc7da65a40aff 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/columns.tsx @@ -77,7 +77,7 @@ export const getHostRiskScoreColumns = ({ if (riskScore != null) { return ( - {riskScore.toFixed(2)} + {Math.round(riskScore)} ); } diff --git a/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx index 1711aee8b1932d..3c95933e0bcbce 100644 --- a/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx @@ -100,7 +100,8 @@ describe('when in the Administration tab', () => { }); }); - describe('when the user has permissions', () => { + // FLAKY: https://github.com/elastic/kibana/issues/145204 + describe.skip('when the user has permissions', () => { it('should display the Management view if user has privileges', async () => { useUserPrivilegesMock.mockReturnValue({ endpointPrivileges: { loading: false, canReadEndpointList: true }, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts index e1885881d81a5c..979bd12b78c201 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts @@ -167,6 +167,17 @@ export const AdvancedPolicySchema: AdvancedPolicySchemaType[] = [ } ), }, + { + key: 'linux.advanced.capture_env_vars', + first_supported_version: '8.6', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.capture_env_vars', + { + defaultMessage: + 'The list of environment variables to capture (up to five), separated by commas.', + } + ), + }, { key: 'linux.advanced.tty_io.max_event_interval_seconds', first_supported_version: '8.5', diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/mock_data.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/mock_data.ts index 4aa8c1766c2f7a..a18546a77fe275 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/mock_data.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/mock_data.ts @@ -30,13 +30,14 @@ export const dataProviderWithOneFilter = [ { and: [], enabled: true, - id: '', + id: 'mock-id', name: 'host.hostname', excluded: false, kqlQuery: '', queryMatch: { field: 'host.hostname', value: 'Host-u6ou715rzy', + displayValue: 'Host-u6ou715rzy', operator: ':' as QueryOperator, }, }, @@ -49,25 +50,27 @@ export const dataProviderWithAndFilters = [ and: [], enabled: true, excluded: false, - id: '', + id: 'mock-id', kqlQuery: '', name: 'kibana.alerts.workflow_status', queryMatch: { field: 'kibana.alerts.workflow_status', operator: ':' as QueryOperator, value: 'open', + displayValue: 'open', }, }, ], enabled: true, - id: '', + id: 'mock-id', name: 'host.hostname', excluded: false, kqlQuery: '', queryMatch: { field: 'host.hostname', value: 'Host-u6ou715rzy', + displayValue: 'Host-u6ou715rzy', operator: ':' as QueryOperator, }, }, @@ -79,25 +82,27 @@ export const dataProviderWithOrFilters = [ { and: [], enabled: true, - id: '', + id: 'mock-id', name: 'kibana.alerts.workflow_status', excluded: false, kqlQuery: '', queryMatch: { field: 'kibana.alerts.workflow_status', value: 'open', + displayValue: 'open', operator: ':' as QueryOperator, }, }, ], enabled: true, - id: '', + id: 'mock-id', name: 'host.hostname', excluded: false, kqlQuery: '', queryMatch: { field: 'host.hostname', value: 'Host-u6ou715rzy', + displayValue: 'Host-u6ou715rzy', operator: ':' as QueryOperator, }, }, @@ -106,25 +111,27 @@ export const dataProviderWithOrFilters = [ { and: [], enabled: true, - id: '', + id: 'mock-id', name: 'kibana.alerts.workflow_status', excluded: false, kqlQuery: '', queryMatch: { field: 'kibana.alerts.workflow_status', value: 'closed', + displayValue: 'closed', operator: ':' as QueryOperator, }, }, ], enabled: true, - id: '', + id: 'mock-id', name: 'host.hostname', excluded: false, kqlQuery: '', queryMatch: { field: 'host.hostname', value: 'Host-u6ou715rzy', + displayValue: 'Host-u6ou715rzy', operator: ':' as QueryOperator, }, }, @@ -133,25 +140,27 @@ export const dataProviderWithOrFilters = [ { and: [], enabled: true, - id: '', + id: 'mock-id', name: 'kibana.alerts.workflow_status', excluded: false, kqlQuery: '', queryMatch: { field: 'kibana.alerts.workflow_status', value: 'acknowledged', + displayValue: 'acknowledged', operator: ':' as QueryOperator, }, }, ], enabled: true, - id: '', + id: 'mock-id', name: 'host.hostname', excluded: false, kqlQuery: '', queryMatch: { field: 'host.hostname', value: 'Host-u6ou715rzy', + displayValue: 'Host-u6ou715rzy', operator: ':' as QueryOperator, }, }, diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.test.ts b/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.test.ts index 25f070b6d14f70..5e451f6c44cc8e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.test.ts +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.test.ts @@ -33,6 +33,10 @@ jest.mock('react-redux', () => { }; }); +jest.mock('uuid', () => ({ + v4: () => 'mock-id', +})); + const id = 'timeline-1'; const renderUseNavigatgeToTimeline = () => renderHook(() => useNavigateToTimeline()); diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx index 705375d48ec3e6..24c9968c7cffa5 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx @@ -7,12 +7,13 @@ import { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; +import { v4 as uuid } from 'uuid'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { sourcererActions } from '../../../../common/store/sourcerer'; import { getDataProvider } from '../../../../common/components/event_details/table/use_action_cell_data_provider'; -import type { DataProvider } from '../../../../../common/types/timeline'; +import type { DataProvider, QueryOperator } from '../../../../../common/types/timeline'; import { TimelineId, TimelineType } from '../../../../../common/types/timeline'; import { useCreateTimeline } from '../../../../timelines/components/timeline/properties/use_create_timeline'; import { updateProviders } from '../../../../timelines/store/timeline/actions'; @@ -21,7 +22,8 @@ import type { TimeRange } from '../../../../common/store/inputs/model'; export interface Filter { field: string; - value: string; + value: string | string[]; + operator?: QueryOperator; } export const useNavigateToTimeline = () => { @@ -79,10 +81,17 @@ export const useNavigateToTimeline = () => { const mainFilter = orFilterGroup[0]; if (mainFilter) { - const dataProvider = getDataProvider(mainFilter.field, '', mainFilter.value); + const dataProvider = getDataProvider( + mainFilter.field, + uuid(), + mainFilter.value, + mainFilter.operator + ); for (const filter of orFilterGroup.slice(1)) { - dataProvider.and.push(getDataProvider(filter.field, '', filter.value)); + dataProvider.and.push( + getDataProvider(filter.field, uuid(), filter.value, filter.operator) + ); } dataProviders.push(dataProvider); } diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.test.tsx index 4bc5b56b6fa3e4..c5eb04940b799f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.test.tsx @@ -22,6 +22,9 @@ import { } from '../../../common/mock'; import { mockTheme } from './mock'; import { tGridReducer } from '@kbn/timelines-plugin/public'; +import { createKibanaContextProviderMock } from '../../../common/lib/kibana/kibana_react.mock'; + +const MockKibanaContextProvider = createKibanaContextProviderMock(); jest.mock('../../../common/lib/kibana'); @@ -53,7 +56,9 @@ describe('CtiDisabledModule', () => { - + + + diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_enabled_module.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_enabled_module.test.tsx index f81334d7e84e40..09cad282168a24 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_enabled_module.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_enabled_module.test.tsx @@ -24,6 +24,9 @@ import { mockTheme, mockProps, mockTiDataSources, mockCtiLinksResponse } from '. import { useCtiDashboardLinks } from '../../containers/overview_cti_links'; import { useTiDataSources } from '../../containers/overview_cti_links/use_ti_data_sources'; import { tGridReducer } from '@kbn/timelines-plugin/public'; +import { createKibanaContextProviderMock } from '../../../common/lib/kibana/kibana_react.mock'; + +const MockKibanaContextProvider = createKibanaContextProviderMock(); jest.mock('../../../common/lib/kibana'); @@ -63,7 +66,9 @@ describe('CtiEnabledModule', () => { - + + + diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/index.test.tsx index c91dbb71296711..96e91946fc5a3f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/index.test.tsx @@ -24,6 +24,9 @@ import { mockTheme, mockProps, mockTiDataSources, mockCtiLinksResponse } from '. import { useTiDataSources } from '../../containers/overview_cti_links/use_ti_data_sources'; import { useCtiDashboardLinks } from '../../containers/overview_cti_links'; import { tGridReducer } from '@kbn/timelines-plugin/public'; +import { createKibanaContextProviderMock } from '../../../common/lib/kibana/kibana_react.mock'; + +const MockKibanaContextProvider = createKibanaContextProviderMock(); jest.mock('../../../common/lib/kibana'); @@ -63,7 +66,9 @@ describe('ThreatIntelLinkPanel', () => { - + + + @@ -71,6 +76,7 @@ describe('ThreatIntelLinkPanel', () => { expect(wrapper.find('[data-test-subj="cti-enabled-module"]').length).toEqual(1); expect(wrapper.find('[data-test-subj="cti-enable-integrations-button"]').length).toEqual(0); + expect(wrapper.find('[data-test-subj="cti-view-indicators"]').length).toBeGreaterThan(0); }); it('renders CtiDisabledModule when Threat Intel module is disabled', () => { @@ -78,7 +84,9 @@ describe('ThreatIntelLinkPanel', () => { - + + + diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx index c6a623f19681fb..df1e034a832f77 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx @@ -8,6 +8,8 @@ import React, { useMemo } from 'react'; import type { EuiTableFieldDataColumnType } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { SecurityPageName } from '../../../../common/constants'; +import { SecuritySolutionLinkButton } from '../../../common/components/links'; import * as i18n from './translations'; import type { LinkPanelListItem } from '../link_panel'; @@ -63,6 +65,20 @@ export const ThreatIntelPanelView: React.FC = ({ ), [totalCount] ), + button: useMemo( + () => ( + + + + ), + [] + ), }} /> ); diff --git a/x-pack/plugins/security_solution/public/risk_score/components/risk_score_onboarding/utils.test.ts b/x-pack/plugins/security_solution/public/risk_score/components/risk_score_onboarding/utils.test.ts index f19c2b2c70b888..cb46c3a255d3aa 100644 --- a/x-pack/plugins/security_solution/public/risk_score/components/risk_score_onboarding/utils.test.ts +++ b/x-pack/plugins/security_solution/public/risk_score/components/risk_score_onboarding/utils.test.ts @@ -7,6 +7,7 @@ import type { HttpSetup } from '@kbn/core/public'; import { RiskScoreEntity } from '../../../../common/search_strategy'; import { + getIngestPipelineName, getLegacyIngestPipelineName, getRiskScoreLatestTransformId, getRiskScorePivotTransformId, @@ -20,7 +21,7 @@ import * as api from '../../containers/onboarding/api'; import { installRiskScoreModule, restartRiskScoreTransforms, - uninstallLegacyRiskScoreModule, + uninstallRiskScoreModule, } from './utils'; jest.mock('../../containers/onboarding/api'); @@ -70,12 +71,12 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])( ); describe.each([[RiskScoreEntity.host], [RiskScoreEntity.user]])( - 'uninstallLegacyRiskScoreModule - %s', + 'uninstallRiskScoreModule - %s', (riskScoreEntity) => { beforeAll(async () => { - await uninstallLegacyRiskScoreModule({ + await uninstallRiskScoreModule({ http: mockHttp, - spaceId: 'customSpace', + spaceId: mockSpaceId, riskScoreEntity, }); }); @@ -99,28 +100,38 @@ describe.each([[RiskScoreEntity.host], [RiskScoreEntity.user]])( it('Delete legacy ingest pipelines', () => { expect((api.deleteIngestPipelines as jest.Mock).mock.calls[0][0].names).toEqual( - getLegacyIngestPipelineName(riskScoreEntity) + [ + getLegacyIngestPipelineName(riskScoreEntity), + getIngestPipelineName(riskScoreEntity, mockSpaceId), + ].join(',') ); }); it('Delete legacy stored scripts', () => { if (riskScoreEntity === RiskScoreEntity.user) { expect((api.deleteStoredScripts as jest.Mock).mock.calls[0][0].ids).toMatchInlineSnapshot(` - Array [ - "ml_userriskscore_levels_script", - "ml_userriskscore_map_script", - "ml_userriskscore_reduce_script", - ] - `); + Array [ + "ml_userriskscore_levels_script", + "ml_userriskscore_map_script", + "ml_userriskscore_reduce_script", + "ml_userriskscore_levels_script_customSpace", + "ml_userriskscore_map_script_customSpace", + "ml_userriskscore_reduce_script_customSpace", + ] + `); } else { expect((api.deleteStoredScripts as jest.Mock).mock.calls[0][0].ids).toMatchInlineSnapshot(` - Array [ - "ml_hostriskscore_levels_script", - "ml_hostriskscore_init_script", - "ml_hostriskscore_map_script", - "ml_hostriskscore_reduce_script", - ] - `); + Array [ + "ml_hostriskscore_levels_script", + "ml_hostriskscore_init_script", + "ml_hostriskscore_map_script", + "ml_hostriskscore_reduce_script", + "ml_hostriskscore_levels_script_customSpace", + "ml_hostriskscore_init_script_customSpace", + "ml_hostriskscore_map_script_customSpace", + "ml_hostriskscore_reduce_script_customSpace", + ] + `); } }); } diff --git a/x-pack/plugins/security_solution/public/risk_score/components/risk_score_onboarding/utils.ts b/x-pack/plugins/security_solution/public/risk_score/components/risk_score_onboarding/utils.ts index beceb9edbb7b5e..ee5eebd109b05c 100644 --- a/x-pack/plugins/security_solution/public/risk_score/components/risk_score_onboarding/utils.ts +++ b/x-pack/plugins/security_solution/public/risk_score/components/risk_score_onboarding/utils.ts @@ -129,7 +129,7 @@ export const installRiskScoreModule = async (settings: InstallRiskScoreModule) = } }; -export const uninstallLegacyRiskScoreModule = async ({ +export const uninstallRiskScoreModule = async ({ http, notifications, refetch, @@ -148,22 +148,39 @@ export const uninstallLegacyRiskScoreModule = async ({ deleteAll?: boolean; }) => { const legacyTransformIds = [ + // transform Ids never changed since 8.3 utils.getRiskScorePivotTransformId(riskScoreEntity, spaceId), utils.getRiskScoreLatestTransformId(riskScoreEntity, spaceId), ]; const legacyRiskScoreHostsScriptIds = [ + // 8.4 utils.getLegacyRiskScoreLevelScriptId(RiskScoreEntity.host), utils.getLegacyRiskScoreInitScriptId(RiskScoreEntity.host), utils.getLegacyRiskScoreMapScriptId(RiskScoreEntity.host), utils.getLegacyRiskScoreReduceScriptId(RiskScoreEntity.host), + // 8.3 and after 8.5 + utils.getRiskScoreLevelScriptId(RiskScoreEntity.host, spaceId), + utils.getRiskScoreInitScriptId(RiskScoreEntity.host, spaceId), + utils.getRiskScoreMapScriptId(RiskScoreEntity.host, spaceId), + utils.getRiskScoreReduceScriptId(RiskScoreEntity.host, spaceId), ]; const legacyRiskScoreUsersScriptIds = [ + // 8.4 utils.getLegacyRiskScoreLevelScriptId(RiskScoreEntity.user), utils.getLegacyRiskScoreMapScriptId(RiskScoreEntity.user), utils.getLegacyRiskScoreReduceScriptId(RiskScoreEntity.user), + // 8.3 and after 8.5 + utils.getRiskScoreLevelScriptId(RiskScoreEntity.user, spaceId), + utils.getRiskScoreMapScriptId(RiskScoreEntity.user, spaceId), + utils.getRiskScoreReduceScriptId(RiskScoreEntity.user, spaceId), ]; - const legacyIngestPipelineNames = [utils.getLegacyIngestPipelineName(riskScoreEntity)]; + const legacyIngestPipelineNames = [ + // 8.4 + utils.getLegacyIngestPipelineName(riskScoreEntity), + // 8.3 and 8.5 + utils.getIngestPipelineName(riskScoreEntity, spaceId), + ]; await Promise.all([ /** @@ -214,7 +231,7 @@ export const uninstallLegacyRiskScoreModule = async ({ * Intended not to pass notification to deleteStoredScripts. * As the only error it can happen is script not found, and * that is what deleteStoredScripts wants. - * (Before 8.5 once a script was created, it was shared across different spaces. + * (In 8.4 once a script was created, it was shared across different spaces. * If it has been upgrade in one space, "script not found" will happen when upgrading other spaces. * Or it could be users manually deleted the script.) */ @@ -243,7 +260,7 @@ export const upgradeHostRiskScoreModule = async ({ theme, timerange, }: UpgradeRiskScoreModule) => { - await uninstallLegacyRiskScoreModule({ + await uninstallRiskScoreModule({ http, notifications, renderDocLink, @@ -276,7 +293,7 @@ export const upgradeUserRiskScoreModule = async ({ theme, timerange, }: UpgradeRiskScoreModule) => { - await uninstallLegacyRiskScoreModule({ + await uninstallRiskScoreModule({ http, notifications, renderDocLink, diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/components.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/components.test.tsx new file mode 100644 index 00000000000000..ff9c524a767322 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/components.test.tsx @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { ControlledComboboxInput, ControlledDefaultInput } from '.'; +import { + convertComboboxValuesToStringArray, + convertValuesToComboboxValueArray, +} from './controlled_combobox_input'; +import { getDefaultValue } from './controlled_default_input'; + +const onChangeCallbackMock = jest.fn(); + +const renderControlledComboboxInput = (badOverrideValue?: string) => + render( + + ); + +const renderControlledDefaultInput = (badOverrideValue?: string[]) => + render( + + ); + +describe('ControlledComboboxInput', () => { + afterEach(jest.clearAllMocks); + + it('renders the current value', () => { + renderControlledComboboxInput(); + expect(screen.getByText('test')); + }); + + it('calls onChangeCallback, and disabledButtonCallback when value is removed', () => { + renderControlledComboboxInput(); + const removeButton = screen.getByTestId('is-one-of-combobox-input').querySelector('button'); + + userEvent.click(removeButton as HTMLButtonElement); + expect(onChangeCallbackMock).toHaveBeenLastCalledWith([]); + }); + + it('handles non arrays by defaulting to an empty state', () => { + renderControlledComboboxInput('nonArray'); + + expect(onChangeCallbackMock).toHaveBeenLastCalledWith([]); + }); +}); + +describe('ControlledDefaultInput', () => { + afterEach(jest.clearAllMocks); + + it('renders the current value', () => { + renderControlledDefaultInput(); + expect(screen.getByDisplayValue('test')); + }); + + it('calls onChangeCallback, and disabledButtonCallback when value is changed', () => { + renderControlledDefaultInput([]); + const inputBox = screen.getByPlaceholderText('value'); + + userEvent.type(inputBox, 'new value'); + + expect(onChangeCallbackMock).toHaveBeenLastCalledWith('new value'); + }); + + it('handles arrays by defaulting to the first value', () => { + renderControlledDefaultInput(['testing']); + + expect(onChangeCallbackMock).toHaveBeenLastCalledWith('testing'); + }); + + describe('getDefaultValue', () => { + it('Returns a provided value if the value is a string', () => { + expect(getDefaultValue('test')).toBe('test'); + }); + + it('Returns a provided value if the value is a number', () => { + expect(getDefaultValue(0)).toBe(0); + }); + + it('Returns the first value of a string array', () => { + expect(getDefaultValue(['a', 'b'])).toBe('a'); + }); + + it('Returns the first value of a number array', () => { + expect(getDefaultValue([0, 1])).toBe(0); + }); + + it('Returns an empty string if given an empty array', () => { + expect(getDefaultValue([])).toBe(''); + }); + }); + + describe('convertValuesToComboboxValueArray', () => { + it('returns an empty array if not provided correct input', () => { + expect(convertValuesToComboboxValueArray('test')).toEqual([]); + expect(convertValuesToComboboxValueArray(1)).toEqual([]); + }); + + it('returns an empty array if provided non array input', () => { + expect(convertValuesToComboboxValueArray('test')).toEqual([]); + expect(convertValuesToComboboxValueArray(1)).toEqual([]); + }); + + it('Returns a comboboxoption array when provided the correct input', () => { + expect(convertValuesToComboboxValueArray(['a', 'b'])).toEqual([ + { label: 'a' }, + { label: 'b' }, + ]); + expect(convertValuesToComboboxValueArray([1, 2])).toEqual([{ label: '1' }, { label: '2' }]); + }); + }); + + describe('convertComboboxValuesToStringArray', () => { + it('correctly converts combobox values to a string array ', () => { + expect(convertComboboxValuesToStringArray([])).toEqual([]); + expect(convertComboboxValuesToStringArray([{ label: '1' }, { label: '2' }])).toEqual([ + '1', + '2', + ]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/controlled_combobox_input.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/controlled_combobox_input.tsx new file mode 100644 index 00000000000000..0be933113e99c9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/controlled_combobox_input.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, { useState, useEffect, useCallback } from 'react'; + +import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import { EuiComboBox } from '@elastic/eui'; + +import { isStringOrNumberArray } from '../../timeline/helpers'; +import * as i18n from '../translations'; + +interface ControlledDataProviderInput { + onChangeCallback: (value: string | number | string[]) => void; + value: string | number | Array; +} + +export const ControlledComboboxInput = ({ + value, + onChangeCallback, +}: ControlledDataProviderInput) => { + const [includeValues, setIncludeValues] = useState(convertValuesToComboboxValueArray(value)); + + useEffect(() => { + onChangeCallback(convertComboboxValuesToStringArray(includeValues)); + }, [includeValues, onChangeCallback]); + + const onCreateOption = useCallback( + (searchValue: string, flattenedOptions: EuiComboBoxOptionOption[] = includeValues) => { + const normalizedSearchValue = searchValue.trim().toLowerCase(); + + if (!normalizedSearchValue) { + return; + } + + if ( + flattenedOptions.findIndex( + (option) => option.label.trim().toLowerCase() === normalizedSearchValue + ) === -1 + // add the option, because it wasn't found in the current set of `includeValues` + ) { + setIncludeValues([ + ...includeValues, + { + label: searchValue, + }, + ]); + } + }, + [includeValues] + ); + + const onIncludeValueChanged = useCallback((updatedIncludesValues: EuiComboBoxOptionOption[]) => { + setIncludeValues(updatedIncludesValues); + }, []); + + return ( + + ); +}; + +export const convertValuesToComboboxValueArray = ( + values: string | number | Array +): EuiComboBoxOptionOption[] => + isStringOrNumberArray(values) ? values.map((item) => ({ label: String(item) })) : []; + +export const convertComboboxValuesToStringArray = (values: EuiComboBoxOptionOption[]): string[] => + values.map((item) => item.label); diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/controlled_default_input.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/controlled_default_input.tsx new file mode 100644 index 00000000000000..9734e1c51de1f3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/controlled_default_input.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, { useState, useEffect, useCallback } from 'react'; + +import { EuiFieldText } from '@elastic/eui'; + +import { isStringOrNumberArray } from '../../timeline/helpers'; +import { sanatizeValue } from '../helpers'; +import * as i18n from '../translations'; + +interface ControlledDataProviderInput { + onChangeCallback: (value: string | number | string[]) => void; + value: string | number | Array; +} + +const VALUE_INPUT_CLASS_NAME = 'edit-data-provider-value'; + +export const ControlledDefaultInput = ({ + value, + onChangeCallback, +}: ControlledDataProviderInput) => { + const [primitiveValue, setPrimitiveValue] = useState(getDefaultValue(value)); + + useEffect(() => { + onChangeCallback(sanatizeValue(primitiveValue)); + }, [primitiveValue, onChangeCallback]); + + const onValueChange = useCallback((e: React.ChangeEvent) => { + setPrimitiveValue(e.target.value); + }, []); + + return ( + + ); +}; + +export const getDefaultValue = ( + value: string | number | Array +): string | number => { + if (isStringOrNumberArray(value)) { + return value[0] ?? ''; + } else return value; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/index.ts b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/index.ts new file mode 100644 index 00000000000000..6d841723bfdf81 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/components/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 { ControlledComboboxInput } from './controlled_combobox_input'; +export { ControlledDefaultInput } from './controlled_default_input'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.test.tsx index e890b724f78b55..1f4a50a46a62f5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.test.tsx @@ -5,14 +5,21 @@ * 2.0. */ +import { DataProviderType } from '@kbn/timelines-plugin/common'; + import { mockBrowserFields } from '../../../common/containers/source/mock'; -import { EXISTS_OPERATOR, IS_OPERATOR } from '../timeline/data_providers/data_provider'; +import { + EXISTS_OPERATOR, + IS_OPERATOR, + IS_ONE_OF_OPERATOR, +} from '../timeline/data_providers/data_provider'; import { getCategorizedFieldNames, getExcludedFromSelection, getFieldNames, getQueryOperatorFromSelection, + sanatizeValue, selectionsAreValid, } from './helpers'; @@ -106,6 +113,9 @@ describe('helpers', () => { { label: 'nestedField.secondAttributes', }, + { + label: 'nestedField.thirdAttributes', + }, ], }, { label: 'source', options: [{ label: 'source.ip' }, { label: 'source.port' }] }, @@ -132,6 +142,7 @@ describe('helpers', () => { label: 'is', }, ], + type: DataProviderType.default, }) ).toBe(true); }); @@ -150,6 +161,7 @@ describe('helpers', () => { label: 'is', }, ], + type: DataProviderType.default, }) ).toBe(false); }); @@ -165,9 +177,10 @@ describe('helpers', () => { ], selectedOperator: [ { - label: 'is', + label: 'is one of', }, ], + type: DataProviderType.default, }) ).toBe(false); }); @@ -186,6 +199,7 @@ describe('helpers', () => { label: '', }, ], + type: DataProviderType.default, }) ).toBe(false); }); @@ -204,6 +218,45 @@ describe('helpers', () => { label: 'invalid-operator', }, ], + type: DataProviderType.default, + }) + ).toBe(false); + }); + + test('it should return false when the selected operator is "is one of", and the DataProviderType is template', () => { + expect( + selectionsAreValid({ + browserFields: mockBrowserFields, + selectedField: [ + { + label: 'destination.bytes', + }, + ], + selectedOperator: [ + { + label: 'is one of', + }, + ], + type: DataProviderType.template, + }) + ).toBe(false); + }); + + test('it should return false when the selected operator is "is not one of", and the DataProviderType is template', () => { + expect( + selectionsAreValid({ + browserFields: mockBrowserFields, + selectedField: [ + { + label: 'destination.bytes', + }, + ], + selectedOperator: [ + { + label: 'is not one of', + }, + ], + type: DataProviderType.template, }) ).toBe(false); }); @@ -219,6 +272,14 @@ describe('helpers', () => { operator: i18n.IS_NOT, expected: IS_OPERATOR, }, + { + operator: i18n.IS_ONE_OF, + expected: IS_ONE_OF_OPERATOR, + }, + { + operator: i18n.IS_NOT_ONE_OF, + expected: IS_ONE_OF_OPERATOR, + }, { operator: i18n.EXISTS, expected: EXISTS_OPERATOR, @@ -230,7 +291,7 @@ describe('helpers', () => { ]; validSelections.forEach(({ operator, expected }) => { - test(`it should the expected operator given "${operator}", a valid selection`, () => { + test(`it should use the expected operator given "${operator}", a valid selection`, () => { expect( getQueryOperatorFromSelection([ { @@ -282,6 +343,15 @@ describe('helpers', () => { ]) ).toBe(false); }); + test('it returns false when the "is one of" operator is selected', () => { + expect( + getExcludedFromSelection([ + { + label: i18n.IS_ONE_OF, + }, + ]) + ).toBe(false); + }); test('it returns false when the "exists" operator is selected', () => { expect( @@ -313,6 +383,16 @@ describe('helpers', () => { ).toBe(true); }); + test('it returns true when "is not one of" is selected', () => { + expect( + getExcludedFromSelection([ + { + label: i18n.IS_NOT_ONE_OF, + }, + ]) + ).toBe(true); + }); + test('it returns true when "does not exist" is selected', () => { expect( getExcludedFromSelection([ @@ -323,4 +403,19 @@ describe('helpers', () => { ).toBe(true); }); }); + + describe('sanatizeValue', () => { + it("returns a provided value if it's a string or number as a string", () => { + expect(sanatizeValue('a string')).toBe('a string'); + expect(sanatizeValue(1)).toBe('1'); + }); + + it('returns the string interpretation of the first value of an array', () => { + expect(sanatizeValue(['a string', 'another value'])).toBe('a string'); + expect(sanatizeValue([1, 'another value'])).toBe('1'); + expect(sanatizeValue([])).toBe(''); + expect(sanatizeValue([null])).toBe('null'); + expect(sanatizeValue([undefined])).toBe('undefined'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.tsx index bcda8c7167bf6f..c2ad001dde1a68 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.tsx @@ -6,12 +6,18 @@ */ import { findIndex } from 'lodash/fp'; + import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import { DataProviderType } from '@kbn/timelines-plugin/common'; import type { BrowserField, BrowserFields } from '../../../common/containers/source'; import { getAllFieldsByName } from '../../../common/containers/source'; import type { QueryOperator } from '../timeline/data_providers/data_provider'; -import { EXISTS_OPERATOR, IS_OPERATOR } from '../timeline/data_providers/data_provider'; +import { + EXISTS_OPERATOR, + IS_OPERATOR, + IS_ONE_OF_OPERATOR, +} from '../timeline/data_providers/data_provider'; import * as i18n from './translations'; @@ -23,6 +29,12 @@ export const operatorLabels: EuiComboBoxOptionOption[] = [ { label: i18n.IS_NOT, }, + { + label: i18n.IS_ONE_OF, + }, + { + label: i18n.IS_NOT_ONE_OF, + }, { label: i18n.EXISTS, }, @@ -57,18 +69,23 @@ export const selectionsAreValid = ({ browserFields, selectedField, selectedOperator, + type, }: { browserFields: BrowserFields; selectedField: EuiComboBoxOptionOption[]; selectedOperator: EuiComboBoxOptionOption[]; + type: DataProviderType; }): boolean => { const fieldId = selectedField.length > 0 ? selectedField[0].label : ''; const operator = selectedOperator.length > 0 ? selectedOperator[0].label : ''; const fieldIsValid = browserFields && getAllFieldsByName(browserFields)[fieldId] != null; const operatorIsValid = findIndex((o) => o.label === operator, operatorLabels) !== -1; + const isOneOfOperatorSelectionWithTemplate = + type === DataProviderType.template && + (operator === i18n.IS_ONE_OF || operator === i18n.IS_NOT_ONE_OF); - return fieldIsValid && operatorIsValid; + return fieldIsValid && operatorIsValid && !isOneOfOperatorSelectionWithTemplate; }; /** Returns a `QueryOperator` based on the user's Operator selection */ @@ -81,6 +98,9 @@ export const getQueryOperatorFromSelection = ( case i18n.IS: // fall through case i18n.IS_NOT: return IS_OPERATOR; + case i18n.IS_ONE_OF: // fall through + case i18n.IS_NOT_ONE_OF: + return IS_ONE_OF_OPERATOR; case i18n.EXISTS: // fall through case i18n.DOES_NOT_EXIST: return EXISTS_OPERATOR; @@ -97,9 +117,19 @@ export const getExcludedFromSelection = (selectedOperator: EuiComboBoxOptionOpti switch (selection) { case i18n.IS_NOT: // fall through + case i18n.IS_NOT_ONE_OF: case i18n.DOES_NOT_EXIST: return true; default: return false; } }; + +/** Ensure that a value passed to ControlledDefaultInput is not an array */ +export const sanatizeValue = (value: string | number | unknown[]): string => { + if (Array.isArray(value)) { + // fun fact: value should never be an array + return value.length ? `${value[0]}` : ''; + } + return `${value}`; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx index 4a92bf323f2ebe..8fd5dce2da0d2a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx @@ -15,6 +15,7 @@ import { DataProviderType, IS_OPERATOR, EXISTS_OPERATOR, + IS_ONE_OF_OPERATOR, } from '../timeline/data_providers/data_provider'; import { StatefulEditDataProvider } from '.'; @@ -144,6 +145,46 @@ describe('StatefulEditDataProvider', () => { expect(screen.getByText('does not exist')).toBeInTheDocument(); }); + test('it renders the "is one of" operator in human-readable format', () => { + render( + + + + ); + + expect(screen.getByText('is one of')).toBeInTheDocument(); + }); + + test('it renders the negated "is one of" operator in a humanized format when isExcluded is true', () => { + render( + + + + ); + + expect(screen.getByText('is not one of')).toBeInTheDocument(); + }); + test('it renders the current value when the operator is "is"', () => { render( @@ -186,6 +227,48 @@ describe('StatefulEditDataProvider', () => { expect(screen.getByDisplayValue(value)).toBeInTheDocument(); }); + test('it handles bad values when the operator is "is one of" by showing default placholder', () => { + const reallyAnArrayOfBadValues = [undefined, null] as unknown as string[]; + render( + + + + ); + expect(screen.getByText('enter one or more values')).toBeInTheDocument(); + }); + + test('it renders selected values when the type of value is an array and the operator is "is one of"', () => { + const values = ['apple', 'banana', 'cherry']; + render( + + + + ); + expect(screen.getByText(values[0])).toBeInTheDocument(); + expect(screen.getByText(values[1])).toBeInTheDocument(); + expect(screen.getByText(values[2])).toBeInTheDocument(); + }); + test('it does NOT render the current value when the operator is "is not" (isExcluded is true)', () => { render( @@ -226,6 +309,27 @@ describe('StatefulEditDataProvider', () => { expect(screen.getByPlaceholderText('value')).toBeInTheDocument(); }); + test('it renders the expected placeholder when value is empty and operator is "is one of"', () => { + render( + + + + ); + + // EuiCombobox does not render placeholder text with placeholder tag + expect(screen.getByText('enter one or more values')).toBeInTheDocument(); + }); + test('it does NOT render value when the operator is "exists"', () => { render( @@ -244,6 +348,7 @@ describe('StatefulEditDataProvider', () => { ); expect(screen.queryByPlaceholderText('value')).not.toBeInTheDocument(); + expect(screen.queryByDisplayValue('Value')).not.toBeInTheDocument(); }); test('it does NOT render value when the operator is "not exists" (isExcluded is true)', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx index 38c27ba4a0d515..29b78e73f7b964 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import { noop, startsWith, endsWith } from 'lodash/fp'; +import { noop } from 'lodash/fp'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiButton, EuiComboBox, - EuiFieldText, + EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -33,6 +33,8 @@ import { selectionsAreValid, } from './helpers'; +import { ControlledComboboxInput, ControlledDefaultInput } from './components'; + import * as i18n from './translations'; const EDIT_DATA_PROVIDER_WIDTH = 400; @@ -55,19 +57,18 @@ interface Props { operator: QueryOperator; providerId: string; timelineId: string; - value: string | number; + value: string | number | Array; type?: DataProviderType; } -const sanatizeValue = (value: string | number): string => - Array.isArray(value) ? `${value[0]}` : `${value}`; // fun fact: value should never be an array - export const getInitialOperatorLabel = ( isExcluded: boolean, operator: QueryOperator ): EuiComboBoxOptionOption[] => { if (operator === ':') { return isExcluded ? [{ label: i18n.IS_NOT }] : [{ label: i18n.IS }]; + } else if (operator === 'includes') { + return isExcluded ? [{ label: i18n.IS_NOT_ONE_OF }] : [{ label: i18n.IS_ONE_OF }]; } else { return isExcluded ? [{ label: i18n.DOES_NOT_EXIST }] : [{ label: i18n.EXISTS }]; } @@ -90,7 +91,33 @@ export const StatefulEditDataProvider = React.memo( const [updatedOperator, setUpdatedOperator] = useState( getInitialOperatorLabel(isExcluded, operator) ); - const [updatedValue, setUpdatedValue] = useState(value); + + const [updatedValue, setUpdatedValue] = useState>( + value + ); + + const showComboBoxInput = useMemo( + () => + updatedOperator.length > 0 && + (updatedOperator[0].label === i18n.IS_ONE_OF || + updatedOperator[0].label === i18n.IS_NOT_ONE_OF), + [updatedOperator] + ); + + const showValueInput = useMemo( + () => + type !== DataProviderType.template && + updatedOperator.length > 0 && + updatedOperator[0].label !== i18n.EXISTS && + updatedOperator[0].label !== i18n.DOES_NOT_EXIST && + !showComboBoxInput, + [showComboBoxInput, type, updatedOperator] + ); + + const disableSave = useMemo( + () => showComboBoxInput && Array.isArray(updatedValue) && !updatedValue.length, + [showComboBoxInput, updatedValue] + ); /** Focuses the Value input if it is visible, falling back to the Save button if it's not */ const focusInput = () => { @@ -126,8 +153,8 @@ export const StatefulEditDataProvider = React.memo( focusInput(); }, []); - const onValueChange = useCallback((e: React.ChangeEvent) => { - setUpdatedValue(e.target.value); + const onValueChange = useCallback((changedValue: string | number | string[]) => { + setUpdatedValue(changedValue); }, []); const disableScrolling = () => { @@ -170,14 +197,6 @@ export const StatefulEditDataProvider = React.memo( type, ]); - const isValueFieldInvalid = useMemo( - () => - type !== DataProviderType.template && - (startsWith('{', sanatizeValue(updatedValue)) || - endsWith('}', sanatizeValue(updatedValue))), - [type, updatedValue] - ); - useEffect(() => { disableScrolling(); return () => { @@ -224,22 +243,6 @@ export const StatefulEditDataProvider = React.memo( /> - {type !== DataProviderType.template && - updatedOperator.length > 0 && - updatedOperator[0].label !== i18n.EXISTS && - updatedOperator[0].label !== i18n.DOES_NOT_EXIST ? ( - - - - - - ) : null} @@ -248,6 +251,35 @@ export const StatefulEditDataProvider = React.memo( + {showValueInput && ( + + + + )} + + {showComboBoxInput && type !== DataProviderType.template && ( + + + + )} + + + + + + + + {type === DataProviderType.template && showComboBoxInput && ( + <> + + + + )} ( fill={true} isDisabled={ !selectionsAreValid({ + type, browserFields, selectedField: updatedField, selectedOperator: updatedOperator, - }) || isValueFieldInvalid + }) || disableSave } onClick={handleSave} size="m" diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/translations.ts index 44d8ee8087ac55..562a69eeb9d0ba 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/translations.ts @@ -33,10 +33,28 @@ export const IS = i18n.translate('xpack.securitySolution.editDataProvider.isLabe defaultMessage: 'is', }); +export const IS_ONE_OF = i18n.translate('xpack.securitySolution.editDataProvider.isOneOfLabel', { + defaultMessage: 'is one of', +}); + export const IS_NOT = i18n.translate('xpack.securitySolution.editDataProvider.isNotLabel', { defaultMessage: 'is not', }); +export const IS_NOT_ONE_OF = i18n.translate( + 'xpack.securitySolution.editDataProvider.isNotOneOfLabel', + { + defaultMessage: 'is not one of', + } +); + +export const ENTER_ONE_OR_MORE_VALUES = i18n.translate( + 'xpack.securitySolution.editDataProvider.includesPlaceholder', + { + defaultMessage: 'enter one or more values', + } +); + export const OPERATOR = i18n.translate('xpack.securitySolution.editDataProvider.operatorLabel', { defaultMessage: 'Operator', }); @@ -59,3 +77,9 @@ export const SELECT_AN_OPERATOR = i18n.translate( defaultMessage: 'Select an operator', } ); + +export const UNAVAILABLE_OPERATOR = (operator: string) => + i18n.translate('xpack.securitySolution.editDataProvider.unavailableOperator', { + values: { operator }, + defaultMessage: '{operator} operator is unavailable with templates', + }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx index 367a145774d7a1..59967eda95cddc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx @@ -111,6 +111,7 @@ const FlyoutHeaderPanelComponent: React.FC = ({ timeline () => !isEmpty(dataProviders) || !isEmpty(get('filterQuery.kuery.expression', kqlQuery)), [dataProviders, kqlQuery] ); + const getKqlQueryTimeline = useMemo(() => timelineSelectors.getKqlFilterQuerySelector(), []); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const kqlQueryTimeline = useSelector((state: State) => getKqlQueryTimeline(state, timelineId)!); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx index 6bee0890274771..f91a9eed0d1651 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx @@ -19,6 +19,7 @@ export interface GetBasicDataFromDetailsData { userName: string; ruleName: string; timestamp: string; + data: TimelineEventsDetailsItem[] | null; } export const useBasicDataFromDetailsData = ( @@ -62,8 +63,9 @@ export const useBasicDataFromDetailsData = ( userName, ruleName, timestamp, + data, }), - [agentId, alertId, hostName, isAlert, ruleName, timestamp, userName] + [agentId, alertId, hostName, isAlert, ruleName, timestamp, userName, data] ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index a7475bab09f14e..12e78df1e49ec0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -626,6 +626,26 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` }, "type": "string", }, + "nestedField.thirdAttributes": Object { + "aggregatable": false, + "category": "nestedField", + "description": "", + "example": "", + "format": "", + "indexes": Array [ + "auditbeat", + "filebeat", + "packetbeat", + ], + "name": "nestedField.thirdAttributes", + "searchable": true, + "subType": Object { + "nested": Object { + "path": "nestedField", + }, + }, + "type": "date", + }, }, }, "source": Object { 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 32251df6318f25..0bff0f502175f5 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 @@ -210,7 +210,8 @@ describe('Body', () => { trailingControlColumns: [], }; - describe('rendering', () => { + // FLAKY: https://github.com/elastic/kibana/issues/145187 + describe.skip('rendering', () => { beforeEach(() => { mockDispatch.mockClear(); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap index 082ed270ada227..4a4c047d4cb1e5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/__snapshots__/netflow_row_renderer.test.tsx.snap @@ -2,12 +2,7 @@ exports[`netflowRowRenderer renders correctly against snapshot 1`] = ` - .c15 svg { - position: relative; - top: -1px; -} - -.c0 { + .c0 { display: inline-block; font-size: 12px; line-height: 1.5; @@ -21,6 +16,11 @@ exports[`netflowRowRenderer renders correctly against snapshot 1`] = ` border-radius: 4px; } +.c15 svg { + position: relative; + top: -1px; +} + .c13, .c13 * { display: inline-block; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx index ea66ac153d3be7..01c06946b5343b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx @@ -36,6 +36,7 @@ export const DefaultCellRenderer: React.FC = ({ setCellProps, scopeId, truncate, + closeCellPopover, }) => { const asPlainText = useMemo(() => { return getLinkColumnDefinition(header.id, header.type, undefined) !== undefined && !isTimeline; @@ -72,6 +73,7 @@ export const DefaultCellRenderer: React.FC = ({ globalFilters={globalFilters} scopeId={scopeId} value={values} + closeCellPopover={closeCellPopover} /> )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/__snapshots__/provider.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/__snapshots__/provider.test.tsx.snap index acc48bdc6c0443..5af5d50dd6f468 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/__snapshots__/provider.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/__snapshots__/provider.test.tsx.snap @@ -3,6 +3,7 @@ exports[`Provider rendering renders correctly against snapshot 1`] = ` = ( onAddedToTimeline: handleClosePopover, providerToAdd: { id: providerId, - name: value, + name: field, enabled: true, excluded, kqlQuery: '', type, queryMatch: { displayField: undefined, - displayValue: undefined, + displayValue: getDisplayValue(value), field, value, operator, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/data_provider.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/data_provider.ts index b96fe3c0db0f9b..204583c9eabaae 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/data_provider.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/data_provider.ts @@ -13,8 +13,11 @@ export const IS_OPERATOR = ':'; /** The `exists` operator in a KQL query */ export const EXISTS_OPERATOR = ':*'; +/** The `is one of` faux operator in a KQL query */ +export const IS_ONE_OF_OPERATOR = 'includes'; + /** The operator applied to a field */ -export type QueryOperator = ':' | ':*'; +export type QueryOperator = typeof IS_OPERATOR | typeof EXISTS_OPERATOR | typeof IS_ONE_OF_OPERATOR; export enum DataProviderType { default = 'default', @@ -24,7 +27,7 @@ export enum DataProviderType { export interface QueryMatch { field: string; displayField?: string; - value: string | number; + value: string | number | Array; displayValue?: string | number; operator: QueryOperator; } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.test.tsx index faafb8eca7eca0..e490cd631f116e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.test.tsx @@ -23,6 +23,7 @@ import { reorder, sourceAndDestinationAreSameDroppable, unFlattenGroups, + getDisplayValue, } from './helpers'; import { providerA, @@ -1103,4 +1104,18 @@ describe('helpers', () => { }); }); }); + + describe('getDisplayValue', () => { + it('converts an array (is one of query) to correct format for a string array', () => { + expect(getDisplayValue(['a', 'b', 'c'])).toBe('( a OR b OR c )'); + expect(getDisplayValue([1, 2, 3])).toBe('( 1 OR 2 OR 3 )'); + }); + it('handles an empty array', () => { + expect(getDisplayValue([])).toBe(''); + }); + it('returns a provided value if not an array', () => { + expect(getDisplayValue(1)).toBe(1); + expect(getDisplayValue('text')).toBe('text'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.tsx index 2af72c36d26ef7..03bd1d84c97082 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/helpers.tsx @@ -10,6 +10,7 @@ import type { DraggableLocation } from 'react-beautiful-dnd'; import type { Dispatch } from 'redux'; import { updateProviders } from '../../../store/timeline/actions'; +import { isStringOrNumberArray } from '../helpers'; import type { DataProvider, DataProvidersAnd } from './data_provider'; @@ -342,3 +343,15 @@ export const addContentToTimeline = ({ }); } }; + +export const getDisplayValue = ( + value: string | number | Array +): string | number => { + if (isStringOrNumberArray(value)) { + if (value.length) { + return `( ${value.join(' OR ')} )`; + } + return ''; + } + return value; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider.tsx index dcfd4337af8038..5b26cff4f6d845 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider.tsx @@ -32,7 +32,8 @@ export const Provider = React.memo(({ dataProvider }) => { toggleExcludedProvider={noop} toggleEnabledProvider={noop} toggleTypeProvider={noop} - val={dataProvider.queryMatch.displayValue || dataProvider.queryMatch.value} + displayValue={String(dataProvider.queryMatch.displayValue ?? dataProvider.queryMatch.value)} + val={dataProvider.queryMatch.value} operator={dataProvider.queryMatch.operator || IS_OPERATOR} type={dataProvider.type || DataProviderType.default} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx index 7e48b4b5c781a5..951caa426e2c1b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx @@ -16,7 +16,7 @@ import { getEmptyString } from '../../../../common/components/empty_value'; import { ProviderContainer } from '../../../../common/components/drag_and_drop/provider_container'; import type { QueryOperator } from './data_provider'; -import { DataProviderType, EXISTS_OPERATOR } from './data_provider'; +import { DataProviderType, EXISTS_OPERATOR, IS_ONE_OF_OPERATOR } from './data_provider'; import * as i18n from './translations'; @@ -102,7 +102,8 @@ interface ProviderBadgeProps { providerId: string; togglePopover: () => void; toggleType: () => void; - val: string | number; + displayValue: string; + val: string | number | Array; operator: QueryOperator; type: DataProviderType; timelineType: TimelineType; @@ -124,6 +125,7 @@ export const ProviderBadge = React.memo( providerId, togglePopover, toggleType, + displayValue, val, type, timelineType, @@ -160,7 +162,9 @@ export const ProviderBadge = React.memo( <> {prefix} {operator !== EXISTS_OPERATOR ? ( - {`${field}: "${formattedValue}"`} + {`${field}: "${ + operator === 'includes' ? displayValue : formattedValue + }"`} ) : ( {field} {i18n.EXISTS_LABEL} @@ -168,7 +172,7 @@ export const ProviderBadge = React.memo( )} ), - [field, formattedValue, operator, prefix] + [displayValue, field, formattedValue, operator, prefix] ); const ariaLabel = useMemo( @@ -196,7 +200,10 @@ export const ProviderBadge = React.memo( {content} - {timelineType === TimelineType.template && ( + {/* Add a UI feature to let users know the is one of operator doesnt work with timeline templates: + https://github.com/elastic/kibana/issues/142437 */} + + {timelineType === TimelineType.template && operator !== IS_ONE_OF_OPERATOR && ( )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx index b78866ceb82abe..d37720f7218c29 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx @@ -16,7 +16,7 @@ import type { BrowserFields } from '../../../../common/containers/source'; import type { OnDataProviderEdited } from '../events'; import type { QueryOperator } from './data_provider'; -import { DataProviderType, EXISTS_OPERATOR } from './data_provider'; +import { DataProviderType, EXISTS_OPERATOR, IS_ONE_OF_OPERATOR } from './data_provider'; import { StatefulEditDataProvider } from '../../edit_data_provider'; import * as i18n from './translations'; @@ -48,7 +48,7 @@ interface OwnProps { toggleEnabledProvider: () => void; toggleExcludedProvider: () => void; toggleTypeProvider: () => void; - value: string | number; + value: string | number | Array; type: DataProviderType; } @@ -80,7 +80,7 @@ interface GetProviderActionsProps { toggleEnabled: () => void; toggleExcluded: () => void; toggleType: () => void; - value: string | number; + value: string | number | Array; type: DataProviderType; } @@ -138,7 +138,7 @@ export const getProviderActions = ({ timelineType === TimelineType.template ? { className: CONVERT_TO_FIELD_CLASS_NAME, - disabled: isLoading, + disabled: isLoading || operator === IS_ONE_OF_OPERATOR, icon: 'visText', name: type === DataProviderType.template diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx index 17d7dc1a6438bf..dfe598177bc550 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx @@ -43,7 +43,8 @@ interface ProviderItemBadgeProps { toggleEnabledProvider: () => void; toggleExcludedProvider: () => void; toggleTypeProvider: () => void; - val: string | number; + displayValue?: string; + val: string | number | Array; type?: DataProviderType; wrapperRef?: React.MutableRefObject; } @@ -67,6 +68,7 @@ export const ProviderItemBadge = React.memo( toggleEnabledProvider, toggleExcludedProvider, toggleTypeProvider, + displayValue, val, type = DataProviderType.default, wrapperRef, @@ -144,6 +146,7 @@ export const ProviderItemBadge = React.memo( providerId={providerId} togglePopover={togglePopover} toggleType={onToggleTypeProvider} + displayValue={displayValue ?? String(val)} val={val} operator={operator} type={type} @@ -152,6 +155,7 @@ export const ProviderItemBadge = React.memo( ), [ deleteProvider, + displayValue, field, isEnabled, isExcluded, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx index eb3e9a2c280b31..d6423b3345835c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx @@ -110,9 +110,6 @@ const ParensContainer = styled(EuiFlexItem)` align-self: center; `; -const getDataProviderValue = (dataProvider: DataProvidersAnd) => - dataProvider.queryMatch.displayValue ?? dataProvider.queryMatch.value; - /** * Renders an interactive card representation of the data providers. It also * affords uniform UI controls for the following actions: @@ -264,6 +261,10 @@ export const DataProvidersGroupItem = React.memo( [onKeyDown] ); + const displayValue = String( + dataProvider.queryMatch.displayValue ?? dataProvider.queryMatch.value + ); + const DraggableContent = useCallback( (provided, snapshot) => (
( toggleEnabledProvider={handleToggleEnabledProvider} toggleExcludedProvider={handleToggleExcludedProvider} toggleTypeProvider={handleToggleTypeProvider} - val={getDataProviderValue(dataProvider)} + displayValue={displayValue} + val={dataProvider.queryMatch.value} type={dataProvider.type} wrapperRef={keyboardHandlerRef} /> @@ -323,6 +325,7 @@ export const DataProvidersGroupItem = React.memo( [ browserFields, dataProvider, + displayValue, group, handleDataProviderEdited, handleDeleteProvider, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts index 5aff599670dc23..46ed0839a0e23e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/events.ts @@ -36,7 +36,7 @@ export type OnDataProviderEdited = ({ id: string; operator: QueryOperator; providerId: string; - value: string | number; + value: string | number | Array; type: DataProvider['type']; }) => void; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx index dc7ed0affa463d..c2df23cd69c4f1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx @@ -7,9 +7,19 @@ import { cloneDeep } from 'lodash/fp'; -import { DataProviderType } from './data_providers/data_provider'; +import { DataProviderType, EXISTS_OPERATOR, IS_OPERATOR } from './data_providers/data_provider'; import { mockDataProviders } from './data_providers/mock/mock_data_providers'; -import { buildGlobalQuery, showGlobalFilters } from './helpers'; + +import { + buildExistsQueryMatch, + buildGlobalQuery, + buildIsOneOfQueryMatch, + buildIsQueryMatch, + handleIsOperator, + isStringOrNumberArray, + showGlobalFilters, +} from './helpers'; + import { mockBrowserFields } from '../../../common/containers/source/mock'; const cleanUpKqlQuery = (str: string) => str.replace(/\n/g, '').replace(/\s\s+/g, ' '); @@ -96,6 +106,24 @@ describe('Build KQL Query', () => { expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : "Provider 2"'); }); + test('Build KQL query with "includes" operator', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].enabled = true; + dataProviders[0].queryMatch.operator = 'includes'; + dataProviders[0].queryMatch.value = ['a', 'b', 'c']; + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual(`name : (\"a\" OR \"b\" OR \"c\")`); + }); + + test('Handles bad inputs to buildKQLQuery', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].enabled = true; + dataProviders[0].queryMatch.operator = 'includes'; + dataProviders[0].queryMatch.value = [undefined] as unknown as string[]; + const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); + expect(cleanUpKqlQuery(kqlQuery)).toEqual('name : [null]'); + }); + test('Build KQL query with two data provider and second is disabled', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); dataProviders[1].enabled = false; @@ -243,3 +271,123 @@ describe('Build KQL Query', () => { }); }); }); + +describe('isStringOrNumberArray', () => { + test('it returns false when value is not an array', () => { + expect(isStringOrNumberArray('just a string')).toBe(false); + }); + + test('it returns false when value is an array of mixed types', () => { + expect(isStringOrNumberArray(['mixed', 123, 'types'])).toBe(false); + }); + test('it returns false when value is an array of bad types', () => { + const badValues = [undefined, null, {}] as unknown as string[]; + expect(isStringOrNumberArray(badValues)).toBe(false); + }); + + test('it returns true when value is an empty array', () => { + expect(isStringOrNumberArray([])).toBe(true); + }); + + test('it returns true when value is an array of all strings', () => { + expect(isStringOrNumberArray(['all', 'string', 'values'])).toBe(true); + }); + + test('it returns true when value is an array of all numbers', () => { + expect(isStringOrNumberArray([123, 456, 789])).toBe(true); + }); + + describe('queryHandlerFunctions', () => { + describe('handleIsOperator', () => { + it('returns the entire query unchanged, if value is an array', () => { + expect( + handleIsOperator({ + browserFields: {}, + field: 'host.name', + isExcluded: '', + isFieldTypeNested: false, + type: undefined, + value: ['some', 'values'], + }) + ).toBe('host.name : ["some","values"]'); + }); + }); + }); + + describe('buildExistsQueryMatch', () => { + it('correcty computes EXISTS query with no nested field', () => { + expect( + buildExistsQueryMatch({ isFieldTypeNested: false, field: 'host', browserFields: {} }) + ).toBe(`host ${EXISTS_OPERATOR}`); + }); + it('correcty computes EXISTS query with nested field', () => { + expect( + buildExistsQueryMatch({ + isFieldTypeNested: true, + field: 'nestedField.firstAttributes', + browserFields: mockBrowserFields, + }) + ).toBe(`nestedField: { firstAttributes: * }`); + }); + }); + + describe('buildIsQueryMatch', () => { + it('correcty computes IS query with no nested field', () => { + expect( + buildIsQueryMatch({ + isFieldTypeNested: false, + field: 'nestedField.thirdAttributes', + value: 100000, + browserFields: {}, + }) + ).toBe(`nestedField.thirdAttributes ${IS_OPERATOR} 100000`); + }); + it('correcty computes IS query with nested date field', () => { + expect( + buildIsQueryMatch({ + isFieldTypeNested: true, + browserFields: mockBrowserFields, + field: 'nestedField.thirdAttributes', + value: 100000, + }) + ).toBe(`nestedField: { thirdAttributes${IS_OPERATOR} \"100000\" }`); + }); + it('correcty computes IS query with nested string field', () => { + expect( + buildIsQueryMatch({ + isFieldTypeNested: true, + browserFields: mockBrowserFields, + field: 'nestedField.secondAttributes', + value: 'text', + }) + ).toBe(`nestedField: { secondAttributes${IS_OPERATOR} text }`); + }); + }); + + describe('buildIsOneOfQueryMatch', () => { + it('correcty computes IS ONE OF query with numbers', () => { + expect( + buildIsOneOfQueryMatch({ + field: 'kibana.alert.worflow_status', + value: [1, 2, 3], + }) + ).toBe('kibana.alert.worflow_status : (1 OR 2 OR 3)'); + }); + it('correcty computes IS ONE OF query with strings', () => { + expect( + buildIsOneOfQueryMatch({ + field: 'kibana.alert.worflow_status', + value: ['a', 'b', 'c'], + }) + ).toBe(`kibana.alert.worflow_status : (\"a\" OR \"b\" OR \"c\")`); + }); + it('correcty computes IS ONE OF query if value is an empty array', () => { + expect( + buildIsOneOfQueryMatch({ + field: 'kibana.alert.worflow_status', + value: [], + }) + ).toBe("kibana.alert.worflow_status : ''"); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx index 9cb0e3d6e60bd0..1760693b4187da 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx @@ -9,21 +9,26 @@ import { isEmpty, get } from 'lodash/fp'; import memoizeOne from 'memoize-one'; import { - handleSkipFocus, elementOrChildrenHasFocus, getFocusedAriaColindexCell, getTableSkipFocus, + handleSkipFocus, stopPropagationAndPreventDefault, } from '@kbn/timelines-plugin/public'; -import { escapeQueryValue } from '../../../common/lib/kuery'; -import type { DataProvider, DataProvidersAnd } from './data_providers/data_provider'; -import { DataProviderType, EXISTS_OPERATOR } from './data_providers/data_provider'; +import { assertUnreachable } from '../../../../common/utility_types'; import type { BrowserFields } from '../../../common/containers/source'; - +import { escapeQueryValue } from '../../../common/lib/kuery'; +import type { DataProvider, DataProvidersAnd } from './data_providers/data_provider'; +import { + DataProviderType, + EXISTS_OPERATOR, + IS_ONE_OF_OPERATOR, + IS_OPERATOR, +} from './data_providers/data_provider'; import { EVENTS_TABLE_CLASS_NAME } from './styles'; -const isNumber = (value: string | number) => !isNaN(Number(value)); +const isNumber = (value: string | number): value is number => !isNaN(Number(value)); const convertDateFieldToQuery = (field: string, value: string | number) => `${field}: ${isNumber(value) ? value : new Date(value).valueOf()}`; @@ -86,27 +91,30 @@ const checkIfFieldTypeIsNested = (field: string, browserFields: BrowserFields) = const buildQueryMatch = ( dataProvider: DataProvider | DataProvidersAnd, browserFields: BrowserFields -) => - `${dataProvider.excluded ? 'NOT ' : ''}${ - dataProvider.queryMatch.operator !== EXISTS_OPERATOR && - dataProvider.type !== DataProviderType.template - ? checkIfFieldTypeIsNested(dataProvider.queryMatch.field, browserFields) - ? convertNestedFieldToQuery( - dataProvider.queryMatch.field, - dataProvider.queryMatch.value, - browserFields - ) - : checkIfFieldTypeIsDate(dataProvider.queryMatch.field, browserFields) - ? convertDateFieldToQuery(dataProvider.queryMatch.field, dataProvider.queryMatch.value) - : `${dataProvider.queryMatch.field} : ${ - isNumber(dataProvider.queryMatch.value) - ? dataProvider.queryMatch.value - : escapeQueryValue(dataProvider.queryMatch.value) - }` - : checkIfFieldTypeIsNested(dataProvider.queryMatch.field, browserFields) - ? convertNestedFieldToExistQuery(dataProvider.queryMatch.field, browserFields) - : `${dataProvider.queryMatch.field} ${EXISTS_OPERATOR}` - }`.trim(); +) => { + const { + excluded, + type, + queryMatch: { field, operator, value }, + } = dataProvider; + + const isFieldTypeNested = checkIfFieldTypeIsNested(field, browserFields); + const isExcluded = excluded ? 'NOT ' : ''; + + switch (operator) { + case IS_OPERATOR: + return handleIsOperator({ browserFields, field, isExcluded, isFieldTypeNested, type, value }); + + case EXISTS_OPERATOR: + return `${isExcluded}${buildExistsQueryMatch({ browserFields, field, isFieldTypeNested })}`; + + case IS_ONE_OF_OPERATOR: + return handleIsOneOfOperator({ field, isExcluded, value }); + + default: + assertUnreachable(operator); + } +}; export const buildGlobalQuery = (dataProviders: DataProvider[], browserFields: BrowserFields) => dataProviders @@ -253,3 +261,94 @@ export const focusUtilityBarAction = (containerElement: HTMLElement | null) => { export const resetKeyboardFocus = () => { document.querySelector('header.headerGlobalNav a.euiHeaderLogo')?.focus(); }; + +interface OperatorHandler { + field: string; + isExcluded: string; + value: string | number | Array; +} + +export const handleIsOperator = ({ + browserFields, + field, + isExcluded, + isFieldTypeNested, + type, + value, +}: OperatorHandler & { + browserFields: BrowserFields; + isFieldTypeNested: boolean; + type?: DataProviderType; +}) => { + if (!isStringOrNumberArray(value)) { + return `${isExcluded}${ + type !== DataProviderType.template + ? buildIsQueryMatch({ browserFields, field, isFieldTypeNested, value }) + : buildExistsQueryMatch({ browserFields, field, isFieldTypeNested }) + }`; + } else { + return `${isExcluded}${field} : ${JSON.stringify(value)}`; + } +}; + +const handleIsOneOfOperator = ({ field, isExcluded, value }: OperatorHandler) => { + if (isStringOrNumberArray(value)) { + return `${isExcluded}${buildIsOneOfQueryMatch({ field, value })}`; + } else { + return `${isExcluded}${field} : ${JSON.stringify(value)}`; + } +}; + +export const buildIsQueryMatch = ({ + browserFields, + field, + isFieldTypeNested, + value, +}: { + browserFields: BrowserFields; + field: string; + isFieldTypeNested: boolean; + value: string | number; +}): string => { + if (isFieldTypeNested) { + return convertNestedFieldToQuery(field, value, browserFields); + } else if (checkIfFieldTypeIsDate(field, browserFields)) { + return convertDateFieldToQuery(field, value); + } else { + return `${field} : ${isNumber(value) ? value : escapeQueryValue(value)}`; + } +}; + +export const buildExistsQueryMatch = ({ + browserFields, + field, + isFieldTypeNested, +}: { + browserFields: BrowserFields; + field: string; + isFieldTypeNested: boolean; +}): string => { + return isFieldTypeNested + ? convertNestedFieldToExistQuery(field, browserFields).trim() + : `${field} ${EXISTS_OPERATOR}`.trim(); +}; + +export const buildIsOneOfQueryMatch = ({ + field, + value, +}: { + field: string; + value: Array; +}): string => { + const trimmedField = field.trim(); + if (value.length) { + return `${trimmedField} : (${value + .map((item) => (isNumber(item) ? Number(item) : `${escapeQueryValue(item.trim())}`)) + .join(' OR ')})`; + } + return `${trimmedField} : ''`; +}; + +export const isStringOrNumberArray = (value: unknown): value is Array => + Array.isArray(value) && + (value.every((x) => typeof x === 'string') || value.every((x) => typeof x === 'number')); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 5324c02fcbfdfe..55c8d2cad65bcc 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -81,7 +81,7 @@ type TimelineResponse = T extends 'kuery' export interface UseTimelineEventsProps { dataViewId: string | null; - endDate: string; + endDate?: string; eqlOptions?: EqlOptionsSelected; fields: string[]; filterQuery?: ESQuery | string; @@ -92,7 +92,7 @@ export interface UseTimelineEventsProps { runtimeMappings: MappingRuntimeFields; skip?: boolean; sort?: TimelineRequestSortField[]; - startDate: string; + startDate?: string; timerangeKind?: 'absolute' | 'relative'; } @@ -360,17 +360,17 @@ export const useTimelineEventsHandler = ({ ...deStructureEqlOptions(prevEqlRequest), }; + const timerange = + startDate && endDate + ? { timerange: { interval: '12h', from: startDate, to: endDate } } + : {}; const currentSearchParameters = { defaultIndex: indexNames, filterQuery: createFilter(filterQuery), querySize: limit, sort, - timerange: { - interval: '12h', - from: startDate, - to: endDate, - }, runtimeMappings, + ...timerange, ...deStructureEqlOptions(eqlOptions), }; @@ -391,11 +391,7 @@ export const useTimelineEventsHandler = ({ language, runtimeMappings, sort, - timerange: { - interval: '12h', - from: startDate, - to: endDate, - }, + ...timerange, ...(eqlOptions ? eqlOptions : {}), }; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index 7ed71e3b6a4a86..39bd6044879b9e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -114,7 +114,7 @@ export const dataProviderEdited = actionCreator<{ id: string; operator: QueryOperator; providerId: string; - value: string | number; + value: string | number | Array; }>('DATA_PROVIDER_EDITED'); export const updateDataProviderType = actionCreator<{ diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts index cfe0e860852b16..3f82606e5b6d6d 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/helpers.ts @@ -50,6 +50,7 @@ import { import { activeTimeline } from '../../containers/active_timeline_context'; import type { ResolveTimelineConfig } from '../../components/open_timeline/types'; import type { SessionViewConfig } from '../../components/timeline/session_tab_content/use_session_view'; +import { getDisplayValue } from '../../components/timeline/data_providers/helpers'; export const isNotNull = (value: T | null): value is T => value !== null; interface AddTimelineNoteParams { @@ -850,7 +851,7 @@ const updateProviderProperties = ({ operator: QueryOperator; providerId: string; timeline: TimelineModel; - value: string | number; + value: string | number | Array; }) => timeline.dataProviders.map((provider) => provider.id === providerId @@ -862,7 +863,7 @@ const updateProviderProperties = ({ field, displayField: field, value, - displayValue: value, + displayValue: getDisplayValue(value), operator, }, } @@ -884,7 +885,7 @@ const updateAndProviderProperties = ({ operator: QueryOperator; providerId: string; timeline: TimelineModel; - value: string | number; + value: string | number | Array; }) => timeline.dataProviders.map((provider) => provider.id === providerId @@ -900,7 +901,7 @@ const updateAndProviderProperties = ({ field, displayField: field, value, - displayValue: value, + displayValue: getDisplayValue(value), operator, }, } @@ -918,7 +919,7 @@ interface UpdateTimelineProviderEditPropertiesParams { operator: QueryOperator; providerId: string; timelineById: TimelineById; - value: string | number; + value: string | number | Array; } export const updateTimelineProviderProperties = ({ diff --git a/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/columns.test.tsx b/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/columns.test.tsx index 8192c2a8e7f5cf..54a90e89e52f5c 100644 --- a/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/columns.test.tsx +++ b/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/columns.test.tsx @@ -41,7 +41,7 @@ describe('getUserRiskScoreColumns', () => { expect(queryByTestId('users-link-anchor')).toHaveTextContent(username); }); - test('should render user score truncated', () => { + test('should render user score rounded', () => { const columns: UserRiskScoreColumns = getUserRiskScoreColumns(defaultProps); const riskScore = 10.11111111; @@ -50,6 +50,6 @@ describe('getUserRiskScoreColumns', () => { const { queryByTestId } = render({renderedColumn}); - expect(queryByTestId('risk-score-truncate')).toHaveTextContent('10.11'); + expect(queryByTestId('risk-score-truncate')).toHaveTextContent('10'); }); }); diff --git a/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/columns.tsx b/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/columns.tsx index c50ae488383f07..aaea29472bb1d6 100644 --- a/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/columns.tsx @@ -78,7 +78,7 @@ export const getUserRiskScoreColumns = ({ if (riskScore != null) { return ( - {riskScore.toFixed(2)} + {Math.round(riskScore)} ); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index d891ad96a57c0a..da1e111a8e5ff5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -48,7 +49,8 @@ "Windows", "Threat Detection", "Collection", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -81,5 +83,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 0c69fba974b396..964c9c86ce87a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -54,7 +55,8 @@ "Windows", "Threat Detection", "Collection", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -82,5 +84,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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_accepted_default_telnet_port_connection.json similarity index 84% rename from x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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_accepted_default_telnet_port_connection.json index 5a7899dd2dd3ab..87cc01836aabf2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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_accepted_default_telnet_port_connection.json @@ -15,14 +15,19 @@ ], "language": "kuery", "license": "Elastic License v2", - "name": "Telnet Port Activity", - "query": "event.category:(network or network_traffic) and network.transport:tcp and destination.port:23\n", + "name": "Accepted Default Telnet Port Connection", + "query": "event.category:(network or network_traffic) and destination.port:23\n and network.direction:(inbound or ingress or outbound or egress)\n and not event.action:(\n flow_dropped or denied or deny or\n flow_terminated or timeout or Reject or network_flow)\n", "required_fields": [ { "ecs": true, "name": "destination.port", "type": "long" }, + { + "ecs": true, + "name": "event.action", + "type": "keyword" + }, { "ecs": true, "name": "event.category", @@ -30,7 +35,7 @@ }, { "ecs": true, - "name": "network.transport", + "name": "network.direction", "type": "keyword" } ], @@ -43,7 +48,9 @@ "Network", "Threat Detection", "Command and Control", - "Host" + "Host", + "Lateral Movement", + "Initial Access" ], "threat": [ { @@ -90,5 +97,5 @@ "timeline_title": "Comprehensive Network Timeline", "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 051990db2549ea..e2ed329ce42e6c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -20,7 +20,8 @@ "query": "event.category:(network OR network_traffic) AND type:(tls OR http) AND network.transport:tcp AND destination.domain:/[a-z]{3}.stage.[0-9]{8}\\..*/\n", "references": [ "https://blog.morphisec.com/fin7-attacks-restaurant-industry", - "https://www.fireeye.com/blog/threat-research/2017/04/fin7-phishing-lnk.html" + "https://www.fireeye.com/blog/threat-research/2017/04/fin7-phishing-lnk.html", + "https://www.elastic.co/security-labs/collecting-cobalt-strike-beacons-with-the-elastic-stack" ], "risk_score": 73, "rule_id": "cf53f532-9cc9-445a-9ae7-fced307ec53c", @@ -63,5 +64,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 43c95ff77503a3..5ab4b6071c86fe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -20,7 +20,8 @@ "https://www.cobaltstrike.com/help-setup-collaboration", "https://www.elastic.co/guide/en/beats/packetbeat/current/configuration-tls.html", "https://www.elastic.co/guide/en/beats/filebeat/7.9/filebeat-module-suricata.html", - "https://www.elastic.co/guide/en/beats/filebeat/7.9/filebeat-module-zeek.html" + "https://www.elastic.co/guide/en/beats/filebeat/7.9/filebeat-module-zeek.html", + "https://www.elastic.co/security-labs/collecting-cobalt-strike-beacons-with-the-elastic-stack" ], "required_fields": [ { @@ -81,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 49562601e33725..068c9df48db9ad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -11,7 +11,7 @@ "license": "Elastic License v2", "name": "Connection to Commonly Abused Web Services", "note": "## Triage and analysis\n\n### Investigating Connection to Commonly Abused Web Services\n\nAdversaries may use an existing, legitimate external Web service as a means for relaying data to/from a compromised\nsystem. Popular websites and social media acting as a mechanism for C2 may give a significant amount of cover due to the\nlikelihood that hosts within a network are already communicating with them prior to a compromise.\n\nThis rule looks for processes outside known legitimate program locations communicating with a list of services that can\nbe abused for exfiltration or command and control.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Verify whether the digital signature exists in the executable.\n- Identify the operation type (upload, download, tunneling, etc.).\n- Retrieve the process executable and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This rule has a high chance to produce false positives because it detects communication with legitimate services. Noisy\nfalse positives can be added as exceptions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", - "query": "network where network.protocol == \"dns\" and\n process.name != null and user.id not in (\"S-1-5-18\", \"S-1-5-19\", \"S-1-5-20\") and\n /* Add new WebSvc domains here */\n dns.question.name :\n (\n \"raw.githubusercontent.*\",\n \"*.pastebin.*\",\n \"*drive.google.*\",\n \"*docs.live.*\",\n \"*api.dropboxapi.*\",\n \"*dropboxusercontent.*\",\n \"*onedrive.*\",\n \"*4shared.*\",\n \"*.file.io\",\n \"*filebin.net\",\n \"*slack-files.com\",\n \"*ghostbin.*\",\n \"*ngrok.*\",\n \"*portmap.*\",\n \"*serveo.net\",\n \"*localtunnel.me\",\n \"*pagekite.me\",\n \"*localxpose.io\",\n \"*notabug.org\",\n \"rawcdn.githack.*\",\n \"paste.nrecom.net\",\n \"zerobin.net\",\n \"controlc.com\",\n \"requestbin.net\",\n \"cdn.discordapp.com\",\n \"discordapp.com\",\n \"discord.com\"\n ) and\n /* Insert noisy false positives here */\n not process.executable :\n (\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\WWAHost.exe\",\n \"?:\\\\Windows\\\\System32\\\\smartscreen.exe\",\n \"?:\\\\Windows\\\\System32\\\\MicrosoftEdgeCP.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\Platform\\\\*\\\\MsMpEng.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Fiddler\\\\Fiddler.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Microsoft VS Code\\\\Code.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\OneDrive\\\\OneDrive.exe\",\n \"?:\\\\Windows\\\\system32\\\\mobsync.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\mobsync.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Discord\\\\app-*\\\\Discord.exe\"\n )\n", + "query": "network where network.protocol == \"dns\" and\n process.name != null and user.id not in (\"S-1-5-18\", \"S-1-5-19\", \"S-1-5-20\") and\n /* Add new WebSvc domains here */\n dns.question.name :\n (\n \"raw.githubusercontent.*\",\n \"*.pastebin.*\",\n \"*drive.google.*\",\n \"*docs.live.*\",\n \"*api.dropboxapi.*\",\n \"*dropboxusercontent.*\",\n \"*onedrive.*\",\n \"*4shared.*\",\n \"*.file.io\",\n \"*filebin.net\",\n \"*slack-files.com\",\n \"*ghostbin.*\",\n \"*ngrok.*\",\n \"*portmap.*\",\n \"*serveo.net\",\n \"*localtunnel.me\",\n \"*pagekite.me\",\n \"*localxpose.io\",\n \"*notabug.org\",\n \"rawcdn.githack.*\",\n \"paste.nrecom.net\",\n \"zerobin.net\",\n \"controlc.com\",\n \"requestbin.net\",\n \"cdn.discordapp.com\",\n \"discordapp.com\",\n \"discord.com\",\n \"script.google.com\",\n \"script.googleusercontent.com\"\n ) and\n /* Insert noisy false positives here */\n not process.executable :\n (\n \"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\WWAHost.exe\",\n \"?:\\\\Windows\\\\System32\\\\smartscreen.exe\",\n \"?:\\\\Windows\\\\System32\\\\MicrosoftEdgeCP.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\Platform\\\\*\\\\MsMpEng.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Fiddler\\\\Fiddler.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Programs\\\\Microsoft VS Code\\\\Code.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\OneDrive\\\\OneDrive.exe\",\n \"?:\\\\Windows\\\\system32\\\\mobsync.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\mobsync.exe\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Discord\\\\app-*\\\\Discord.exe\"\n )\n", "required_fields": [ { "ecs": true, @@ -96,5 +96,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 89945ae91c6da2..cd022f4a9507ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "kuery", "license": "Elastic License v2", @@ -48,7 +49,8 @@ "Windows", "Threat Detection", "Command and Control", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -81,5 +83,5 @@ "value": 15 }, "type": "threshold", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 7898871bb9b3a5..7dc51ab662073d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,13 +7,14 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Port Forwarding Rule Addition", "note": "## Triage and analysis\n\n### Investigating Port Forwarding Rule Addition\n\nNetwork port forwarding is a mechanism to redirect incoming TCP connections (IPv4 or IPv6) from the local TCP port to\nany other port number, or even to a port on a remote computer.\n\nAttackers may configure port forwarding rules to bypass network segmentation restrictions, using the host as a jump box\nto access previously unreachable systems.\n\nThis rule monitors the modifications to the `HKLM\\SYSTEM\\*ControlSet*\\Services\\PortProxy\\v4tov4\\` subkeys.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account and system owners and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Identify the target host IP address, check the connections originating from the host where the modification occurred,\nand inspect the credentials used.\n - Investigate suspicious login activity, such as unauthorized access and logins from outside working hours and unusual locations.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the Administrator is aware of the activity\nand there are justifications for this configuration.\n- If this rule is noisy in your environment due to expected activity, consider adding exceptions \u2014 preferably with a combination\nof user and command line conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Delete the port forwarding rule.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", - "query": "registry where registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\PortProxy\\\\v4tov4\\\\*\"\n", + "query": "registry where registry.path : (\n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\PortProxy\\\\v4tov4\\\\*\",\n \"\\\\REGISTRY\\\\MACHINE\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\PortProxy\\\\v4tov4\\\\*\"\n)\n", "references": [ "https://www.fireeye.com/blog/threat-research/2019/01/bypassing-network-restrictions-through-rdp-tunneling.html" ], @@ -34,7 +35,8 @@ "Windows", "Threat Detection", "Command and Control", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -55,5 +57,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a9af94cec09d34..0a4fe9394c5477 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -39,7 +40,8 @@ "Windows", "Threat Detection", "Command and Control", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -60,5 +62,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c49f3cd7efefbf..ecafc5f77b8434 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -49,7 +50,8 @@ "Windows", "Threat Detection", "Command and Control", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -70,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 229755609cc383..d89ab690b166b8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -50,7 +51,8 @@ "Windows", "Threat Detection", "Command and Control", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -71,5 +73,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 82e7f299ea8fa8..86f93eb3446e29 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -44,7 +45,8 @@ "Windows", "Threat Detection", "Command and Control", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -70,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 854e7c05f1ee32..680b1d0d7ef3cc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,7 +14,8 @@ "query": "event.dataset:okta.system and event.action:user.mfa.attempt_bypass\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -65,5 +66,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 1e0e64288a25d4..66bdd8611d63d3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -17,7 +17,8 @@ "query": "event.dataset:okta.system and event.action:user.account.lock\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -73,5 +74,5 @@ "value": 3 }, "type": "threshold", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 30103c4bf3c5ce..f30c336db2616a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -59,7 +60,8 @@ "Windows", "Threat Detection", "Credential Access", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -92,5 +94,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index fcdd974e6de155..ef99cd1102210e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -8,7 +8,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -18,7 +19,8 @@ "query": "process where event.type == \"start\" and\n (\n (process.pe.original_file_name in (\"Cmd.Exe\", \"PowerShell.EXE\", \"XCOPY.EXE\") and\n process.args : (\"copy\", \"xcopy\", \"Copy-Item\", \"move\", \"cp\", \"mv\")\n ) or\n (process.pe.original_file_name : \"esentutl.exe\" and process.args : (\"*/y*\", \"*/vss*\", \"*/d*\"))\n ) and\n process.args : (\"*\\\\ntds.dit\", \"*\\\\config\\\\SAM\", \"\\\\*\\\\GLOBALROOT\\\\Device\\\\HarddiskVolumeShadowCopy*\\\\*\", \"*/system32/config/SAM*\")\n", "references": [ "https://thedfirreport.com/2020/11/23/pysa-mespinoza-ransomware/", - "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1003.002/T1003.002.md#atomic-test-3---esentutlexe-sam-copy" + "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1003.002/T1003.002.md#atomic-test-3---esentutlexe-sam-copy", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -46,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "Elastic Endgame" ], "threat": [ { @@ -74,5 +77,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 5117d4d48b9a0e..bdf2553f55a878 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -39,7 +40,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "Elastic Endgame" ], "threat": [ { @@ -72,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 49a04cb198d866..ad8cca6497114b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -15,7 +16,8 @@ "note": "## Triage and analysis\n\n### Investigating Credential Acquisition via Registry Hive Dumping\n\nDumping registry hives is a common way to access credential information as some hives store credential material.\n\nFor example, the SAM hive stores locally cached credentials (SAM Secrets), and the SECURITY hive stores domain cached\ncredentials (LSA secrets).\n\nDumping these hives in combination with the SYSTEM hive enables the attacker to decrypt these secrets.\n\nThis rule identifies the usage of `reg.exe` to dump SECURITY and/or SAM hives, which potentially indicates the\ncompromise of the credentials stored in the host.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate if the credential material was exfiltrated or processed locally by other tools.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (e.g., 4624) to the target\nhost.\n\n### False positive analysis\n\n- Administrators can export registry hives for backup purposes using command line tools like `reg.exe`. Check whether\nthe user is legitamitely performing this kind of activity.\n\n### Related rules\n\n- Registry Hive File Creation via SMB - a4c7473a-5cb4-4bc1-9d06-e4a75adbc494\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.pe.original_file_name == \"reg.exe\" and\n process.args : (\"save\", \"export\") and\n process.args : (\"hklm\\\\sam\", \"hklm\\\\security\")\n", "references": [ - "https://medium.com/threatpunter/detecting-attempts-to-steal-passwords-from-the-registry-7512674487f8" + "https://medium.com/threatpunter/detecting-attempts-to-steal-passwords-from-the-registry-7512674487f8", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -44,7 +46,8 @@ "Windows", "Threat Detection", "Credential Access", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -77,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c83f8cfc984ef6..de2e199ca83814 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -49,7 +50,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "Elastic Endgame" ], "threat": [ { @@ -70,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index d2bdcb9392ea84..725a6c26482a50 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -50,7 +51,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "Elastic Endgame" ], "threat": [ { @@ -71,5 +73,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index bda2c0ad53d279..c0d35a12070d59 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "query": "event.dataset:azure.activitylogs and azure.activitylogs.operation_name:\"MICROSOFT.KEYVAULT/VAULTS/WRITE\" and event.outcome:(Success or success)\n", "references": [ "https://docs.microsoft.com/en-us/azure/key-vault/general/basic-concepts", - "https://docs.microsoft.com/en-us/azure/key-vault/general/secure-your-key-vault" + "https://docs.microsoft.com/en-us/azure/key-vault/general/secure-your-key-vault", + "https://www.elastic.co/security-labs/detect-credential-access" ], "related_integrations": [ { @@ -82,5 +83,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 5b8b2a5f1ecc8c..b70c2bf7b823f1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -44,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "Elastic Endgame" ], "threat": [ { @@ -74,5 +76,5 @@ "timeline_title": "Comprehensive File Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index eb1af7292bdfa2..6fefcf83d5ddc8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "https://twitter.com/jsecurity101/status/1227987828534956033?s=20", "https://attack.mitre.org/techniques/T1003/001/", "https://threathunterplaybook.com/notebooks/windows/06_credential_access/WIN-170105221010.html", - "http://findingbad.blogspot.com/2017/" + "http://findingbad.blogspot.com/2017/", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -90,5 +91,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 219dacafba9d96..21dbe96ce77818 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -13,7 +13,8 @@ "note": "", "query": "sequence by user.email with maxspan=10m\n [any where event.module == \"okta\" and event.action == \"user.mfa.okta_verify.deny_push\"]\n [any where event.module == \"okta\" and event.action == \"user.mfa.okta_verify.deny_push\"]\n [any where event.module == \"okta\" and event.action == \"user.authentication.sso\"]\n", "references": [ - "https://www.mandiant.com/resources/russian-targeting-gov-business" + "https://www.mandiant.com/resources/russian-targeting-gov-business", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "required_fields": [ { @@ -62,5 +63,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 5fa244d380ae1a..6b81462a7597ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,13 +7,17 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Mimikatz Memssp Log File Detected", "note": "## Triage and analysis\n\n### Investigating Mimikatz Memssp Log File Detected\n\n[Mimikatz](https://github.com/gentilkiwi/mimikatz) is an open-source tool used to collect, decrypt, and/or use cached\ncredentials. This tool is commonly abused by adversaries during the post-compromise stage where adversaries have gained\nan initial foothold on an endpoint and are looking to elevate privileges and seek out additional authentication objects\nsuch as tokens/hashes/credentials that can then be used to laterally move and pivot across a network.\n\nThis rule looks for the creation of a file named `mimilsa.log`, which is generated when using the Mimikatz misc::memssp\nmodule, which injects a malicious Windows SSP to collect locally authenticated credentials, which includes the computer\naccount password, running service credentials, and any accounts that logon.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (e.g., 4624) to the target\nhost.\n- Retrieve and inspect the log file contents.\n- Search for DLL files created in the same location as the log file, and retrieve unsigned DLLs.\n - Use the PowerShell Get-FileHash cmdlet to get the SHA-256 hash value of these files.\n - Search for the existence of these files in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n - Identify the process that created the DLL using file creation events.\n\n### False positive analysis\n\n- This file name `mimilsa.log` should not legitimately be created.\n\n### Related rules\n\n- Mimikatz Powershell Module Activity - ac96ceb8-4399-4191-af1d-4feeac1f1f46\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the host is a Domain Controller (DC):\n - Activate your incident response plan for total Active Directory compromise.\n - Review the privileges assigned to users that can access the DCs to ensure that the least privilege principle is\n being followed and reduce the attack surface.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reboot the host to remove the injected SSP from memory.\n- Reimage the host operating system or restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "file where file.name : \"mimilsa.log\" and process.name : \"lsass.exe\"\n", + "references": [ + "https://www.elastic.co/security-labs/detect-credential-access" + ], "required_fields": [ { "ecs": true, @@ -36,7 +40,8 @@ "Windows", "Threat Detection", "Credential Access", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -57,5 +62,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index f78eead09dea80..9156286051cdc5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -15,7 +15,8 @@ "query": "event.category:process and\npowershell.file.script_block_text:(\n (DumpCreds and\n DumpCerts) or\n \"sekurlsa::logonpasswords\" or\n (\"crypto::certificates\" and\n \"CERT_SYSTEM_STORE_LOCAL_MACHINE\")\n)\n", "references": [ "https://attack.mitre.org/software/S0002/", - "https://raw.githubusercontent.com/EmpireProject/Empire/master/data/module_source/credentials/Invoke-Mimikatz.ps1" + "https://raw.githubusercontent.com/EmpireProject/Empire/master/data/module_source/credentials/Invoke-Mimikatz.ps1", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -67,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index d5a13d5f0285f1..3041946ef7853e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,17 +7,19 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Modification of WDigest Security Provider", "note": "## Triage and analysis\n\n### Investigating Modification of WDigest Security Provider\n\nIn Windows XP, Microsoft added support for a protocol known as WDigest. The WDigest protocol allows clients to send\ncleartext credentials to Hypertext Transfer Protocol (HTTP) and Simple Authentication Security Layer (SASL) applications\nbased on RFC 2617 and 2831. Windows versions up to 8 and 2012 store logon credentials in memory in plaintext by default,\nwhich is no longer the case with newer Windows versions.\n\nStill, attackers can force WDigest to store the passwords insecurely on the memory by modifying the\n`HKLM\\SYSTEM\\*ControlSet*\\Control\\SecurityProviders\\WDigest\\UseLogonCredential` registry key. This activity is\ncommonly related to the execution of credential dumping tools.\n\n#### Possible investigation steps\n\n- It is unlikely that the monitored registry key was modified legitimately in newer versions of Windows. Analysts should\ntreat any activity triggered from this rule with high priority as it typically represents an active adversary.\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Determine if credential dumping tools were run on the host, and retrieve and analyze suspicious executables:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences on other hosts.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (for example, 4624) to the target\nhost after the registry modification.\n\n### False positive analysis\n\n- This modification should not happen legitimately. Any potential benign true positive (B-TP) should be mapped and\nmonitored by the security team, as these modifications expose the entire domain to credential compromises and\nconsequently unauthorized access.\n\n### Related rules\n\n- Mimikatz Powershell Module Activity - ac96ceb8-4399-4191-af1d-4feeac1f1f46\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", - "query": "registry where event.type : (\"creation\", \"change\") and\n registry.path :\n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\SecurityProviders\\\\WDigest\\\\UseLogonCredential\"\n and registry.data.strings : (\"1\", \"0x00000001\") and\n not (process.executable : \"?:\\\\Windows\\\\System32\\\\svchost.exe\" and user.id : \"S-1-5-18\")\n", + "query": "registry where event.type : (\"creation\", \"change\") and\n registry.path : (\n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\SecurityProviders\\\\WDigest\\\\UseLogonCredential\",\n \"\\\\REGISTRY\\\\MACHINE\\\\SYSTEM\\\\*ControlSet*\\\\Control\\\\SecurityProviders\\\\WDigest\\\\UseLogonCredential\"\n ) and registry.data.strings : (\"1\", \"0x00000001\") and\n not (process.executable : \"?:\\\\Windows\\\\System32\\\\svchost.exe\" and user.id : \"S-1-5-18\")\n", "references": [ "https://www.csoonline.com/article/3438824/how-to-detect-and-halt-credential-theft-via-windows-wdigest.html", "https://www.praetorian.com/blog/mitigating-mimikatz-wdigest-cleartext-credential-theft?edition=2019", - "https://frsecure.com/compromised-credentials-response-playbook" + "https://frsecure.com/compromised-credentials-response-playbook", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -56,7 +58,8 @@ "Windows", "Threat Detection", "Credential Access", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -84,5 +87,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 9da46393325811..362863fb1bb8d0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -11,7 +11,10 @@ "license": "Elastic License v2", "name": "Windows Registry File Creation in SMB Share", "note": "## Triage and analysis\n\n### Investigating Windows Registry File Creation in SMB Share\n\nDumping registry hives is a common way to access credential information. Some hives store credential material, as is the\ncase for the SAM hive, which stores locally cached credentials (SAM secrets), and the SECURITY hive, which stores domain\ncached credentials (LSA secrets). Dumping these hives in combination with the SYSTEM hive enables the attacker to\ndecrypt these secrets.\n\nAttackers can try to evade detection on the host by transferring this data to a system that is not\nmonitored to be parsed and decrypted. This rule identifies the creation or modification of a medium-size registry hive\nfile on an SMB share, which may indicate this kind of exfiltration attempt.\n\n#### Possible investigation steps\n\n- Investigate other alerts associated with the user/source host during the past 48 hours.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Inspect the source host for suspicious or abnormal behaviors in the alert timeframe.\n- Capture the registry file(s) to determine the extent of the credential compromise in an eventual incident response.\n\n### False positive analysis\n\n- Administrators can export registry hives for backup purposes. Check whether the user should be performing this kind of\nactivity and is aware of it.\n\n### Related rules\n\n- Credential Acquisition via Registry Hive Dumping - a7e7bfa3-088e-4f13-b29e-3986e0e756b8\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system and restore compromised files to clean versions.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", - "query": "file where event.type == \"creation\" and\n /* regf file header */\n file.Ext.header_bytes : \"72656766*\" and file.size >= 30000 and\n process.pid == 4 and user.id : \"s-1-5-21*\"\n", + "query": "file where event.type == \"creation\" and\n /* regf file header */\n file.Ext.header_bytes : \"72656766*\" and file.size >= 30000 and\n process.pid == 4 and user.id : (\"S-1-5-21*\", \"S-1-12-1-*\")\n", + "references": [ + "https://www.elastic.co/security-labs/detect-credential-access" + ], "required_fields": [ { "ecs": true, @@ -99,5 +102,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a5a2a5c4b5f667..2a38ef6156cb35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -17,7 +17,8 @@ "query": "event.dataset:okta.system and event.category:authentication and event.outcome:failure\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -78,5 +79,5 @@ "value": 25 }, "type": "threshold", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index ae51a710c11364..269add22b580d4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -8,12 +8,13 @@ ], "from": "now-9m", "index": [ - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Network Logon Provider Registry Modification", - "query": "registry where registry.data.strings != null and\n registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\*\\\\NetworkProvider\\\\ProviderPath\" and\n /* Excluding default NetworkProviders RDPNP, LanmanWorkstation and webclient. */\n not ( user.id : \"S-1-5-18\" and\n registry.data.strings in\n (\"%SystemRoot%\\\\System32\\\\ntlanman.dll\",\n \"%SystemRoot%\\\\System32\\\\drprov.dll\",\n \"%SystemRoot%\\\\System32\\\\davclnt.dll\")\n )\n", + "query": "registry where registry.data.strings != null and\n registry.path : (\n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\*\\\\NetworkProvider\\\\ProviderPath\",\n \"\\\\REGISTRY\\\\MACHINE\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\*\\\\NetworkProvider\\\\ProviderPath\"\n ) and\n /* Excluding default NetworkProviders RDPNP, LanmanWorkstation and webclient. */\n not ( user.id : \"S-1-5-18\" and\n registry.data.strings in\n (\"%SystemRoot%\\\\System32\\\\ntlanman.dll\",\n \"%SystemRoot%\\\\System32\\\\drprov.dll\",\n \"%SystemRoot%\\\\System32\\\\davclnt.dll\")\n )\n", "references": [ "https://github.com/gtworek/PSBits/tree/master/PasswordStealing/NPPSpy", "https://docs.microsoft.com/en-us/windows/win32/api/npapi/nf-npapi-nplogonnotify" @@ -44,7 +45,8 @@ "Windows", "Threat Detection", "Persistence", - "Credential Access" + "Credential Access", + "Elastic Endgame" ], "threat": [ { @@ -80,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index cb696cde2123fe..88b82cdfcef87d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -43,7 +44,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "Elastic Endgame" ], "threat": [ { @@ -64,5 +66,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 1b6a0498a8f52c..95f3cd9e92305a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -13,9 +13,10 @@ "license": "Elastic License v2", "name": "Potential Remote Credential Access via Registry", "note": "## Triage and analysis\n\n### Investigating Potential Remote Credential Access via Registry\n\nDumping registry hives is a common way to access credential information. Some hives store credential material,\nsuch as the SAM hive, which stores locally cached credentials (SAM secrets), and the SECURITY hive, which stores domain\ncached credentials (LSA secrets). Dumping these hives in combination with the SYSTEM hive enables the attacker to\ndecrypt these secrets.\n\nAttackers can use tools like secretsdump.py or CrackMapExec to dump the registry hives remotely, and use dumped\ncredentials to access other systems in the domain.\n\n#### Possible investigation steps\n\n- Identify the specifics of the involved assets, such as their role, criticality, and associated users.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Determine the privileges of the compromised accounts.\n- Investigate other alerts associated with the user/source host during the past 48 hours.\n- Investigate potentially compromised accounts. Analysts can do this by searching for login events (e.g., 4624) to the target\nhost.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Any activity that triggered the alert and is not inherently malicious\nmust be monitored by the security team.\n\n### Related rules\n\n- Credential Acquisition via Registry Hive Dumping - a7e7bfa3-088e-4f13-b29e-3986e0e756b8\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine if other hosts were compromised.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Reimage the host operating system or restore the compromised files to clean versions.\n- Ensure that the machine has the latest security updates and is not running unsupported Windows versions.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", - "query": "sequence by host.id, user.id with maxspan=1m\n [authentication where\n event.outcome == \"success\" and event.action == \"logged-in\" and\n winlog.logon.type == \"Network\" and not user.name == \"ANONYMOUS LOGON\" and\n not user.domain == \"NT AUTHORITY\" and source.ip != \"127.0.0.1\" and source.ip !=\"::1\"]\n [file where event.action == \"creation\" and process.name : \"svchost.exe\" and\n file.Ext.header_bytes : \"72656766*\" and user.id : \"S-1-5-21-*\" and file.size >= 30000 and\n not file.path :\n (\"?:\\\\Windows\\\\system32\\\\HKEY_LOCAL_MACHINE_SOFTWARE_Microsoft_*.registry\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\Windows\\\\UsrClass.dat.LOG?\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\Windows\\\\UsrClass.dat\",\n \"?:\\\\Users\\\\*\\\\ntuser.dat.LOG?\",\n \"?:\\\\Users\\\\*\\\\NTUSER.DAT\")]\n", + "query": "sequence by host.id, user.id with maxspan=1m\n [authentication where\n event.outcome == \"success\" and event.action == \"logged-in\" and\n winlog.logon.type == \"Network\" and not user.name == \"ANONYMOUS LOGON\" and\n not user.domain == \"NT AUTHORITY\" and source.ip != \"127.0.0.1\" and source.ip !=\"::1\"]\n [file where event.action == \"creation\" and process.name : \"svchost.exe\" and\n file.Ext.header_bytes : \"72656766*\" and user.id : (\"S-1-5-21-*\", \"S-1-12-1-*\") and file.size >= 30000 and\n not file.path :\n (\"?:\\\\Windows\\\\system32\\\\HKEY_LOCAL_MACHINE_SOFTWARE_Microsoft_*.registry\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\Windows\\\\UsrClass.dat.LOG?\",\n \"?:\\\\Users\\\\*\\\\AppData\\\\Local\\\\Microsoft\\\\Windows\\\\UsrClass.dat\",\n \"?:\\\\Users\\\\*\\\\ntuser.dat.LOG?\",\n \"?:\\\\Users\\\\*\\\\NTUSER.DAT\")]\n", "references": [ - "https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py" + "https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -132,5 +133,5 @@ } ], "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a971b81bd246f7..f4824fb4b4f73a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,7 +14,8 @@ "note": "", "query": "sequence by host.id, winlog.process.pid with maxspan=1s\n\n /* 2 consecutive vault reads from same pid for web creds */\n\n [any where event.code : \"5382\" and\n (winlog.event_data.SchemaFriendlyName : \"Windows Web Password Credential\" or winlog.event_data.Resource : \"http*\") and\n not winlog.event_data.SubjectLogonId : \"0x3e7\"]\n\n [any where event.code : \"5382\" and\n (winlog.event_data.SchemaFriendlyName : \"Windows Web Password Credential\" or winlog.event_data.Resource : \"http*\") and\n not winlog.event_data.SubjectLogonId : \"0x3e7\"]\n", "references": [ - "https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=5382" + "https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=5382", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -89,5 +90,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 3f3febef5cc387..3752c6265d26d7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -16,7 +17,8 @@ "query": "process where event.type == \"start\" and\n (process.pe.original_file_name:\"vaultcmd.exe\" or process.name:\"vaultcmd.exe\") and\n process.args:\"/list*\"\n", "references": [ "https://medium.com/threatpunter/detecting-adversary-tradecraft-with-image-load-event-logging-and-eql-8de93338c16", - "https://web.archive.org/web/20201004080456/https://rastamouse.me/blog/rdp-jump-boxes/" + "https://web.archive.org/web/20201004080456/https://rastamouse.me/blog/rdp-jump-boxes/", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -49,7 +51,8 @@ "Host", "Windows", "Threat Detection", - "Credential Access" + "Credential Access", + "Elastic Endgame" ], "threat": [ { @@ -82,5 +85,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 6759145450ba64..5d3f424f0a7866 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,7 +14,8 @@ "note": "", "query": "process where event.code == \"10\" and\n winlog.event_data.TargetImage : \"?:\\\\WINDOWS\\\\system32\\\\lsass.exe\" and\n\n /* DLLs exporting MiniDumpWriteDump API to create an lsass mdmp*/\n winlog.event_data.CallTrace : (\"*dbghelp*\", \"*dbgcore*\") and\n\n /* case of lsass crashing */\n not process.executable : (\"?:\\\\Windows\\\\System32\\\\WerFault.exe\", \"?:\\\\Windows\\\\System32\\\\WerFaultSecure.exe\")\n", "references": [ - "https://www.ired.team/offensive-security/credential-access-and-credential-dumping/dump-credentials-from-lsass-process-without-mimikatz" + "https://www.ired.team/offensive-security/credential-access-and-credential-dumping/dump-credentials-from-lsass-process-without-mimikatz", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -75,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index d8e9a61e71b4ef..864c199c4812e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -15,7 +15,8 @@ "query": "sequence by host.id, winlog.event_data.SubjectLogonId with maxspan=1m\n [iam where event.action == \"logged-in-special\" and\n winlog.event_data.PrivilegeList : \"SeBackupPrivilege\" and\n\n /* excluding accounts with existing privileged access */\n not winlog.event_data.PrivilegeList : \"SeDebugPrivilege\"]\n [any where event.action == \"Detailed File Share\" and winlog.event_data.RelativeTargetName : \"winreg\"]\n", "references": [ "https://github.com/mpgn/BackupOperatorToDA", - "https://raw.githubusercontent.com/Wh04m1001/Random/main/BackupOperators.cpp" + "https://raw.githubusercontent.com/Wh04m1001/Random/main/BackupOperators.cpp", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -97,5 +98,5 @@ } ], "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 0286e35f7d049a..f37273c047e424 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -11,7 +11,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -56,7 +57,8 @@ "Windows", "Threat Detection", "Credential Access", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -77,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index db1954bf32d482..087621759f73cc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -15,7 +15,8 @@ "note": "", "query": "event.dataset:okta.system and event.action:user.session.impersonation.initiate\n", "references": [ - "https://blog.cloudflare.com/cloudflare-investigation-of-the-january-2022-okta-compromise/" + "https://blog.cloudflare.com/cloudflare-investigation-of-the-january-2022-okta-compromise/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -61,5 +62,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_wireless_creds_dumping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_wireless_creds_dumping.json new file mode 100644 index 00000000000000..e345cca501947f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_wireless_creds_dumping.json @@ -0,0 +1,97 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies attempts to dump Wireless saved access keys in clear text using the Windows built-in utility Netsh.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*", + "endgame-*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Wireless Credential Dumping using Netsh Command", + "note": "", + "query": "process where event.type == \"start\" and\n (process.name : \"netsh.exe\" or process.pe.original_file_name == \"netsh.exe\") and\n process.args : \"wlan\" and process.args : \"key*clear\"\n", + "references": [ + "https://learn.microsoft.com/en-us/windows-server/networking/technologies/netsh/netsh-contexts", + "https://www.geeksforgeeks.org/how-to-find-the-wi-fi-password-using-cmd-in-windows/" + ], + "required_fields": [ + { + "ecs": true, + "name": "event.type", + "type": "keyword" + }, + { + "ecs": true, + "name": "process.args", + "type": "keyword" + }, + { + "ecs": true, + "name": "process.name", + "type": "keyword" + }, + { + "ecs": true, + "name": "process.pe.original_file_name", + "type": "keyword" + } + ], + "risk_score": 73, + "rule_id": "2de87d72-ee0c-43e2-b975-5f0b029ac600", + "setup": "If enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.", + "severity": "high", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Credential Access", + "Discovery", + "Elastic Endgame" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0006", + "name": "Credential Access", + "reference": "https://attack.mitre.org/tactics/TA0006/" + }, + "technique": [ + { + "id": "T1003", + "name": "OS Credential Dumping", + "reference": "https://attack.mitre.org/techniques/T1003/" + }, + { + "id": "T1555", + "name": "Credentials from Password Stores", + "reference": "https://attack.mitre.org/techniques/T1555/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "technique": [ + { + "id": "T1082", + "name": "System Information Discovery", + "reference": "https://attack.mitre.org/techniques/T1082/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/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 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 index b3a48d5e8101b5..22801fa2812237 100644 --- a/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 +++ 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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -79,5 +81,5 @@ "timeline_title": "Comprehensive Process Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 52adb1756ad504..bae3a8f8e9d51e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,13 +7,14 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Modification of AmsiEnable Registry Key", "note": "## Triage and analysis\n\n### Investigating Modification of AmsiEnable Registry Key\n\nThe Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and\nservices to integrate with any antimalware product that's present on a machine. AMSI provides integration with multiple\nWindows components, ranging from User Account Control (UAC) to VBA Macros.\n\nSince AMSI is widely used across security products for increased visibility, attackers can disable it to evade\ndetections that rely on it.\n\nThis rule monitors the modifications to the Software\\Microsoft\\Windows Script\\Settings\\AmsiEnable registry key.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate the execution of scripts and macros after the registry modification.\n- Retrieve scripts or Microsoft Office files and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n- Use process name, command line, and file hash to search for occurrences on other hosts.\n\n### False positive analysis\n\n- This modification should not happen legitimately. Any potential benign true positive (B-TP) should be mapped and\nmonitored by the security team, as these modifications expose the host to malware infections.\n\n### Related rules\n\n- Microsoft Windows Defender Tampering - fe794edd-487f-4a90-b285-3ee54f2af2d3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Delete or set the key to its default value.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", - "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : (\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\",\n \"HKU\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\"\n ) and\n registry.data.strings: (\"0\", \"0x00000000\")\n", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path : (\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\",\n \"HKU\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\",\n \"\\\\REGISTRY\\\\USER\\\\*\\\\Software\\\\Microsoft\\\\Windows Script\\\\Settings\\\\AmsiEnable\"\n ) and\n registry.data.strings: (\"0\", \"0x00000000\")\n", "references": [ "https://hackinparis.com/data/slides/2019/talks/HIP2019-Dominic_Chell-Cracking_The_Perimeter_With_Sharpshooter.pdf", "https://docs.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-portal" @@ -45,7 +46,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -73,5 +75,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 48b117098e8901..8d812354ba2ae9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/network/network-zones.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -77,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a94413ddbd695d..1413e023cc05a7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/network/network-zones.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -77,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 972c69b5052da4..8cc79799763e8f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,13 +7,14 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Clearing Windows Console History", "note": "## Triage and analysis\n\n### Investigating Clearing Windows Console History\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can try to cover their tracks by clearing PowerShell console history. PowerShell has two different ways of\nlogging commands: the built-in history and the command history managed by the PSReadLine module. This rule looks for the\nexecution of commands that can clear the built-in PowerShell logs or delete the `ConsoleHost_history.txt` file.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n - Verify if any other anti-forensics behaviors were observed.\n- Investigate the PowerShell logs on the SIEM to determine if there was suspicious behavior that an attacker may be\ntrying to cover up.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n - Ensure that PowerShell auditing policies and log collection are in place to grant future visibility.", - "query": "process where event.action == \"start\" and\n (process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") or process.pe.original_file_name == \"PowerShell.EXE\") and\n (process.args : \"*Clear-History*\" or\n (process.args : (\"*Remove-Item*\", \"rm\") and process.args : (\"*ConsoleHost_history.txt*\", \"*(Get-PSReadlineOption).HistorySavePath*\")) or\n (process.args : \"*Set-PSReadlineOption*\" and process.args : \"*SaveNothing*\"))\n", + "query": "process where event.type == \"start\" and\n (process.name : (\"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\") or process.pe.original_file_name == \"PowerShell.EXE\") and\n (process.args : \"*Clear-History*\" or\n (process.args : (\"*Remove-Item*\", \"rm\") and process.args : (\"*ConsoleHost_history.txt*\", \"*(Get-PSReadlineOption).HistorySavePath*\")) or\n (process.args : \"*Set-PSReadlineOption*\" and process.args : \"*SaveNothing*\"))\n", "references": [ "https://stefanos.cloud/kb/how-to-clear-the-powershell-command-history/", "https://www.shellhacks.com/clear-history-powershell/", @@ -22,7 +23,7 @@ "required_fields": [ { "ecs": true, - "name": "event.action", + "name": "event.type", "type": "keyword" }, { @@ -51,7 +52,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -79,5 +81,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index ff068224efa5a2..b65e1ed9488b3f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -46,7 +47,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -74,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 2264c363bb04ce..e7b617a2b14cb3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,13 +10,14 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Creation or Modification of Root Certificate", "note": "## Triage and analysis\n\n### Investigating Creation or Modification of Root Certificate\n\nRoot certificates are the primary level of certifications that tell a browser that the communication is trusted and\nlegitimate. This verification is based upon the identification of a certification authority. Windows\nadds several trusted root certificates so browsers can use them to communicate with websites.\n\n[Check out this post](https://www.thewindowsclub.com/what-are-root-certificates-windows) for more details on root certificates and the involved cryptography.\n\nThis rule identifies the creation or modification of a root certificate by monitoring registry modifications. The\ninstallation of a malicious root certificate would allow an attacker the ability to masquerade malicious files as valid\nsigned components from any entity (for example, Microsoft). It could also allow an attacker to decrypt SSL traffic.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate abnormal behaviors observed by the subject process such as network connections, other registry or file\nmodifications, and any spawned child processes.\n- If one of the processes is suspicious, retrieve it and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This detection may be triggered by certain applications that install root certificates for the purpose of inspecting\nSSL traffic. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove the malicious certificate from the root certificate store.\n- Remove and block malicious artifacts identified during triage.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", - "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path :\n (\n \"HKLM\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\"\n ) and\n not process.executable :\n (\"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\*.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\*.exe\",\n \"?:\\\\Windows\\\\Sysmon64.exe\",\n \"?:\\\\Windows\\\\Sysmon.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\Platform\\\\*\\\\MsMpEng.exe\",\n \"?:\\\\Windows\\\\WinSxS\\\\*.exe\",\n \"?:\\\\Windows\\\\UUS\\\\amd64\\\\MoUsoCoreWorker.exe\")\n", + "query": "registry where event.type in (\"creation\", \"change\") and\n registry.path :\n (\n \"HKLM\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"HKLM\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\",\n \"\\\\REGISTRY\\\\MACHINE\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"\\\\REGISTRY\\\\MACHINE\\\\Software\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\",\n \"\\\\REGISTRY\\\\MACHINE\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\Root\\\\Certificates\\\\*\\\\Blob\",\n \"\\\\REGISTRY\\\\MACHINE\\\\Software\\\\Policies\\\\Microsoft\\\\SystemCertificates\\\\AuthRoot\\\\Certificates\\\\*\\\\Blob\"\n ) and\n not process.executable :\n (\"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\*.exe\",\n \"?:\\\\Windows\\\\SysWOW64\\\\*.exe\",\n \"?:\\\\Windows\\\\Sysmon64.exe\",\n \"?:\\\\Windows\\\\Sysmon.exe\",\n \"?:\\\\ProgramData\\\\Microsoft\\\\Windows Defender\\\\Platform\\\\*\\\\MsMpEng.exe\",\n \"?:\\\\Windows\\\\WinSxS\\\\*.exe\",\n \"?:\\\\Windows\\\\UUS\\\\amd64\\\\MoUsoCoreWorker.exe\")\n", "references": [ "https://posts.specterops.io/code-signing-certificate-cloning-attacks-and-defenses-6f98657fc6ec", "https://www.ired.team/offensive-security/persistence/t1130-install-root-certificate" @@ -48,7 +49,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -76,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 5b39ffc36ed8ee..50c20d1704322d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,13 +7,14 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Windows Defender Disabled via Registry Modification", "note": "## Triage and analysis\n\n### Investigating Windows Defender Disabled via Registry Modification\n\nMicrosoft Windows Defender is an antivirus product built into Microsoft Windows, which makes it popular across multiple\nenvironments. Disabling it is a common step in threat actor playbooks.\n\nThis rule monitors the registry for configurations that disable Windows Defender or the start of its service.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check if this operation was approved and performed according to the organization's change management policy.\n\n### False positive analysis\n\n- This mechanism can be used legitimately. Analysts can dismiss the alert if the administrator is aware of the activity,\nthe configuration is justified (for example, it is being used to deploy other security solutions or troubleshooting),\nand no other suspicious activity has been observed.\n\n### Related rules\n\n- Disabling Windows Defender Security Settings via PowerShell - c8cccb06-faf2-4cd5-886e-2c9636cfcb87\n- Microsoft Windows Defender Tampering - fe794edd-487f-4a90-b285-3ee54f2af2d3\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Re-enable Windows Defender and restore the service configurations to automatic start.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Review the privileges assigned to the user to ensure that the least privilege principle is being followed.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", - "query": "registry where event.type in (\"creation\", \"change\") and\n (\n (\n registry.path:\"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\" and\n registry.data.strings: (\"1\", \"0x00000001\")\n ) or\n (\n registry.path:\"HKLM\\\\System\\\\*ControlSet*\\\\Services\\\\WinDefend\\\\Start\" and\n registry.data.strings in (\"3\", \"4\", \"0x00000003\", \"0x00000004\")\n )\n ) and\n\n not process.executable :\n (\"?:\\\\WINDOWS\\\\system32\\\\services.exe\",\n \"?:\\\\Windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Program Files (x86)\\\\Trend Micro\\\\Security Agent\\\\NTRmv.exe\")\n", + "query": "registry where event.type in (\"creation\", \"change\") and\n (\n (\n registry.path: (\n \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\",\n \"\\\\REGISTRY\\\\MACHINE\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\\\\DisableAntiSpyware\"\n ) and\n registry.data.strings: (\"1\", \"0x00000001\")\n ) or\n (\n registry.path: (\n \"HKLM\\\\System\\\\*ControlSet*\\\\Services\\\\WinDefend\\\\Start\",\n \"\\\\REGISTRY\\\\MACHINE\\\\System\\\\*ControlSet*\\\\Services\\\\WinDefend\\\\Start\"\n ) and\n registry.data.strings in (\"3\", \"4\", \"0x00000003\", \"0x00000004\")\n )\n ) and\n\n not process.executable :\n (\"?:\\\\WINDOWS\\\\system32\\\\services.exe\",\n \"?:\\\\Windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Program Files (x86)\\\\Trend Micro\\\\Security Agent\\\\NTRmv.exe\")\n", "references": [ "https://thedfirreport.com/2020/12/13/defender-control/" ], @@ -49,7 +50,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -82,5 +84,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 5a74bfc9b1664d..c5ba0b7f97dddf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -49,7 +50,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -104,5 +106,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 4f816cefa327e8..e4f439a2c10967 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -45,7 +46,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -73,5 +75,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index f4c3273953dcaf..271084ccfda2c6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,13 +7,14 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "PowerShell Script Block Logging Disabled", "note": "## Triage and analysis\n\n### Investigating PowerShell Script Block Logging Disabled\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks, making\nit available in various environments and creating an attractive way for attackers to execute code.\n\nPowerShell Script Block Logging is a feature of PowerShell that records the content of all script blocks that it\nprocesses, giving defenders visibility of PowerShell scripts and sequences of executed commands.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check whether it makes sense for the user to use PowerShell to complete tasks.\n- Investigate if PowerShell scripts were run after logging was disabled.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Related rules\n\n- PowerShell Suspicious Discovery Related Windows API Functions - 61ac3638-40a3-44b2-855a-985636ca985e\n- PowerShell Keylogging Script - bd2c86a0-8b61-4457-ab38-96943984e889\n- PowerShell Suspicious Script with Audio Capture Capabilities - 2f2f4939-0b34-40c2-a0a3-844eb7889f43\n- Potential Process Injection via PowerShell - 2e29e96a-b67c-455a-afe4-de6183431d0d\n- Suspicious .NET Reflection via PowerShell - e26f042e-c590-4e82-8e05-41e81bd822ad\n- PowerShell Suspicious Payload Encoded and Compressed - 81fe9dc6-a2d7-4192-a2d8-eed98afc766a\n- PowerShell Suspicious Script with Screenshot Capabilities - 959a7353-1129-4aa7-9084-30746b256a70\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- Review the privileges assigned to the involved users to ensure that the least privilege principle is being followed.\n- Restrict PowerShell usage outside of IT and engineering business units using GPOs, AppLocker, Intune, or similar software.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", - "query": "registry where event.type == \"change\" and\n registry.path :\n \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\PowerShell\\\\ScriptBlockLogging\\\\EnableScriptBlockLogging\"\n and registry.data.strings : (\"0\", \"0x00000000\")\n", + "query": "registry where event.type == \"change\" and\n registry.path : (\n \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\PowerShell\\\\ScriptBlockLogging\\\\EnableScriptBlockLogging\",\n \"\\\\REGISTRY\\\\MACHINE\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\PowerShell\\\\ScriptBlockLogging\\\\EnableScriptBlockLogging\"\n ) and registry.data.strings : (\"0\", \"0x00000000\")\n", "references": [ "https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.PowerShell::EnableScriptBlockLogging" ], @@ -44,7 +45,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -72,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a07759a72e73f2..47b80721be84c5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -41,7 +42,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -69,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 21c0bd54b831c0..566590bc6ffee3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -52,7 +53,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -80,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index b6c0b9ceceb535..145e215b333580 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -9,7 +9,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -52,7 +53,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -92,5 +94,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 30fcfcf0c8c864..2bba09ab05dc71 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -44,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -65,5 +67,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 61b9caf627c65d..165403e70f8b86 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -68,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 5ddaa9cdac60ab..f184949f33a713 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -46,7 +47,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -74,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 5013d8a5d731a1..d6c580dd85bdd3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -44,7 +45,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -72,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 4ce963325cd9e8..948516a25043f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -43,7 +44,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -71,5 +73,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 03e36246ac907f..1da65f677d24c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -48,7 +49,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -71,5 +73,5 @@ "timeline_title": "Comprehensive Process Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a3a651443340c9..b4865f1cf385b6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -47,7 +48,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -84,5 +86,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index f3131371bc3590..239923e9cc33c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -48,7 +49,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -85,5 +87,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index e01d287ef6b822..2bd98aeed43bb8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -43,7 +44,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -80,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index d6ee39d46a212d..33b762fee9bebc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -43,7 +44,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -71,5 +73,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 87db8907a1b33b..671c5cc6e496d1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -46,7 +47,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -74,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index bf988d5890bd70..efba66b2ebbf66 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -45,7 +46,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -66,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 33af72a319c5bf..d22d70bb3f47cc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -11,7 +11,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -52,7 +53,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -80,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index b41b3adf028e76..ce9f33670e5c1e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -50,7 +51,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -100,5 +102,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 5b3b8514b02366..55ff02621b1109 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -68,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a774917e65abe9..43b78e3533df21 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -5,7 +5,8 @@ "description": "Identifies registry write modifications to hide an encoded portable executable. This could be indicative of adversary defense evasion by avoiding the storing of malicious content directly on disk.", "from": "now-9m", "index": [ - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -26,7 +27,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -52,5 +54,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index b4389ddf8c9bd5..4115fa7f669fee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -51,7 +52,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -79,5 +81,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 35765d1c9884c3..3b4423397079bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "System Log File Deletion", "note": "", - "query": "file where event.type == \"deletion\" and\n file.path :\n (\n \"/var/run/utmp\",\n \"/var/log/wtmp\",\n \"/var/log/btmp\",\n \"/var/log/lastlog\",\n \"/var/log/faillog\",\n \"/var/log/syslog\",\n \"/var/log/messages\",\n \"/var/log/secure\",\n \"/var/log/auth.log\"\n ) and\n not process.name : (\"gzip\")\n", + "query": "file where event.type == \"deletion\" and\n file.path :\n (\n \"/var/run/utmp\",\n \"/var/log/wtmp\",\n \"/var/log/btmp\",\n \"/var/log/lastlog\",\n \"/var/log/faillog\",\n \"/var/log/syslog\",\n \"/var/log/messages\",\n \"/var/log/secure\",\n \"/var/log/auth.log\",\n \"/var/log/boot.log\",\n \"/var/log/kern.log\"\n ) and\n not process.name : (\"gzip\")\n", "references": [ "https://www.fireeye.com/blog/threat-research/2020/11/live-off-the-land-an-overview-of-unc1945.html" ], @@ -56,12 +56,19 @@ { "id": "T1070", "name": "Indicator Removal on Host", - "reference": "https://attack.mitre.org/techniques/T1070/" + "reference": "https://attack.mitre.org/techniques/T1070/", + "subtechnique": [ + { + "id": "T1070.002", + "name": "Clear Linux or Mac System Logs", + "reference": "https://attack.mitre.org/techniques/T1070/002/" + } + ] } ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 49cc483694e290..bec7f128932a92 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -61,5 +63,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index f70226e3a17fe3..e5f59806e1a2b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -68,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c0b3af94f33d14..383841d89c6234 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -53,7 +54,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -74,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 430a1921553a89..193bdcb9119fbd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -35,7 +36,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -63,5 +65,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 1d50e5192b479c..cea767e2897647 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -6,13 +6,14 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "MS Office Macro Security Registry Modifications", "note": "## Triage and analysis\n\n### Investigating MS Office Macro Security Registry Modifications\n\nMacros are small programs that are used to automate repetitive tasks in Microsoft Office applications.\nHistorically, macros have been used for a variety of reasons -- from automating part of a job, to\nbuilding entire processes and data flows. Macros are written in Visual Basic for Applications (VBA) and are saved as\npart of Microsoft Office files.\n\nMacros are often created for legitimate reasons, but they can also be written by attackers to gain access, harm a\nsystem, or bypass other security controls such as application allow listing. In fact, exploitation from malicious macros\nis one of the top ways that organizations are compromised today. These attacks are often conducted through phishing or\nspear phishing campaigns.\n\nAttackers can convince victims to modify Microsoft Office security settings, so their macros are trusted by default and\nno warnings are displayed when they are executed. These settings include:\n\n* *Trust access to the VBA project object model* - When enabled, Microsoft Office will trust all macros and run any code\nwithout showing a security warning or requiring user permission.\n* *VbaWarnings* - When set to 1, Microsoft Office will trust all macros and run any code without showing a security\nwarning or requiring user permission.\n\nThis rule looks for registry changes affecting the conditions above.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the user and check if the change was done manually.\n- Verify whether malicious macros were executed after the registry change.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve recently executed Office documents and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity should not happen legitimately. The security team should address any potential benign true\npositives (B-TPs), as this configuration can put the user and the domain at risk.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Reset the registry key value.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Explore using GPOs to manage security settings for Microsoft Office macros.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", - "query": "registry where event.type == \"change\" and\n registry.path : (\n \"HKU\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\AccessVBOM\",\n \"HKU\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\VbaWarnings\"\n ) and\n registry.data.strings == \"0x00000001\" and\n process.name : (\"cscript.exe\", \"wscript.exe\", \"mshta.exe\", \"mshta.exe\", \"winword.exe\", \"excel.exe\")\n", + "query": "registry where event.type == \"change\" and\n registry.path : (\n \"HKU\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\AccessVBOM\",\n \"HKU\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\VbaWarnings\",\n \"HKU\\\\S-1-12-1-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\AccessVBOM\",\n \"HKU\\\\S-1-12-1-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\VbaWarnings\",\n \"\\\\REGISTRY\\\\USER\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\AccessVBOM\",\n \"\\\\REGISTRY\\\\USER\\\\S-1-5-21-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\VbaWarnings\",\n \"\\\\REGISTRY\\\\USER\\\\S-1-12-1-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\AccessVBOM\",\n \"\\\\REGISTRY\\\\USER\\\\S-1-12-1-*\\\\SOFTWARE\\\\Microsoft\\\\Office\\\\*\\\\Security\\\\VbaWarnings\"\n ) and\n registry.data.strings : (\"0x00000001\", \"1\") and\n process.name : (\"cscript.exe\", \"wscript.exe\", \"mshta.exe\", \"mshta.exe\", \"winword.exe\", \"excel.exe\")\n", "required_fields": [ { "ecs": true, @@ -45,7 +46,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -88,5 +90,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c615e34089ee6b..baf0396462f349 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/Security_Policies.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -77,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 81d8782e6ce596..931551f8a8799a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/Security_Policies.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -77,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 86cd009618c3a5..1ac3123fd3fbf5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/Security_Policies.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -77,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 295fb493e6ed28..af9033e7057fa3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/Security_Policies.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -77,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 15a51b88c0975b..7ad38aa9f345fa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/network/network-zones.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -77,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 1a69f862b69547..1de3a586559cdc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -17,7 +17,8 @@ "query": "event.dataset:okta.system and event.action:policy.lifecycle.update\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -76,5 +77,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 358419263a30e8..2c657d681c5632 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/Security_Policies.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -77,5 +78,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_persistence_account_tokenfilterpolicy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_persistence_account_tokenfilterpolicy.json new file mode 100644 index 00000000000000..ff73dc9d33df0c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_persistence_account_tokenfilterpolicy.json @@ -0,0 +1,88 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies registry modification to the LocalAccountTokenFilterPolicy policy. If this value exists (which doesn't by default) and is set to 1, then remote connections from all local members of Administrators are granted full high-integrity tokens during negotiation.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-endpoint.events.*", + "logs-windows.*", + "endgame-*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Local Account TokenFilter Policy Disabled", + "query": "registry where registry.path : (\n \"HKLM\\\\*\\\\LocalAccountTokenFilterPolicy\",\n \"\\\\REGISTRY\\\\MACHINE\\\\*\\\\LocalAccountTokenFilterPolicy\") and\n registry.data.strings : (\"1\", \"0x00000001\")\n", + "references": [ + "https://www.stigviewer.com/stig/windows_server_2008_r2_member_server/2014-04-02/finding/V-36439", + "https://posts.specterops.io/pass-the-hash-is-dead-long-live-localaccounttokenfilterpolicy-506c25a7c167", + "https://www.welivesecurity.com/wp-content/uploads/2018/01/ESET_Turla_Mosquito.pdf" + ], + "required_fields": [ + { + "ecs": true, + "name": "registry.data.strings", + "type": "wildcard" + }, + { + "ecs": true, + "name": "registry.path", + "type": "keyword" + } + ], + "risk_score": 47, + "rule_id": "07b1ef73-1fde-4a49-a34a-5dd40011b076", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Defense Evasion", + "Privilege Escalation", + "Elastic Endgame" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1112", + "name": "Modify Registry", + "reference": "https://attack.mitre.org/techniques/T1112/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/", + "subtechnique": [ + { + "id": "T1078.003", + "name": "Local Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 686591b1738abc..ab990ab4a7875e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -19,7 +19,8 @@ "references": [ "https://github.com/EmpireProject/Empire/blob/master/data/module_source/management/Invoke-PSInject.ps1", "https://github.com/EmpireProject/Empire/blob/master/data/module_source/management/Invoke-ReflectivePEInjection.ps1", - "https://github.com/BC-SECURITY/Empire/blob/master/empire/server/data/module_source/credentials/Invoke-Mimikatz.ps1" + "https://github.com/BC-SECURITY/Empire/blob/master/empire/server/data/module_source/credentials/Invoke-Mimikatz.ps1", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -76,5 +77,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 48ae03aa6f70f6..66d15946cd8496 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "Potential Process Herpaderping Attempt", - "query": "sequence with maxspan=5s\n [process where event.type == \"start\" and not process.parent.executable : \"C:\\\\Windows\\\\SoftwareDistribution\\\\*.exe\"] by host.id, process.executable, process.parent.entity_id\n [file where event.type == \"change\" and event.action == \"overwrite\" and file.extension == \"exe\"] by host.id, file.path, process.entity_id\n", + "query": "sequence with maxspan=5s\n [process where event.type == \"start\" and not process.parent.executable :\n (\n \"?:\\\\Windows\\\\SoftwareDistribution\\\\*.exe\",\n \"?:\\\\Program Files\\\\Elastic\\\\Agent\\\\data\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\Trend Micro\\\\*.exe\"\n )\n ] by host.id, process.executable, process.parent.entity_id\n [file where event.type == \"change\" and event.action == \"overwrite\" and file.extension == \"exe\"] by host.id, file.path, process.entity_id\n", "references": [ "https://github.com/jxy-s/herpaderping" ], @@ -91,5 +91,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 9f870734840dda..18f2a41384fbb7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -55,7 +56,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -83,5 +85,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 17b8dbcc082364..d589c216ea72db 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -5,12 +5,13 @@ "description": "Identifies a process termination event quickly followed by the deletion of its executable file. Malware tools and other non-native files dropped or created on a system by an adversary may leave traces to indicate to what occurred. Removal of these files can occur during an intrusion, or as part of a post-intrusion process to minimize the adversary's footprint.", "from": "now-9m", "index": [ - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Process Termination followed by Deletion", - "query": "sequence by host.id with maxspan=5s\n [process where event.type == \"end\" and\n process.code_signature.trusted == false and\n not process.executable : (\"C:\\\\Windows\\\\SoftwareDistribution\\\\*.exe\", \"C:\\\\Windows\\\\WinSxS\\\\*.exe\")\n ] by process.executable\n [file where event.type == \"deletion\" and file.extension : (\"exe\", \"scr\", \"com\") and\n not process.executable :\n (\"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Windows\\\\System32\\\\drvinst.exe\") and\n not file.path : (\"?:\\\\Program Files\\\\*.exe\", \"?:\\\\Program Files (x86)\\\\*.exe\")\n ] by file.path\n", + "query": "sequence by host.id with maxspan=5s\n [process where event.type == \"end\" and\n process.code_signature.trusted != true and\n not process.executable : (\"C:\\\\Windows\\\\SoftwareDistribution\\\\*.exe\", \"C:\\\\Windows\\\\WinSxS\\\\*.exe\")\n ] by process.executable\n [file where event.type == \"deletion\" and file.extension : (\"exe\", \"scr\", \"com\") and\n not process.executable :\n (\"?:\\\\Program Files\\\\*.exe\",\n \"?:\\\\Program Files (x86)\\\\*.exe\",\n \"?:\\\\Windows\\\\System32\\\\svchost.exe\",\n \"?:\\\\Windows\\\\System32\\\\drvinst.exe\") and\n not file.path : (\"?:\\\\Program Files\\\\*.exe\", \"?:\\\\Program Files (x86)\\\\*.exe\")\n ] by file.path\n", "required_fields": [ { "ecs": true, @@ -51,7 +52,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -78,5 +80,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index ddffe659f0b416..2f20f27261bfe4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -59,7 +60,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -80,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c0317fc5939a6f..27c5e63f7e7adb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,13 +7,14 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Scheduled Tasks AT Command Enabled", "note": "", - "query": "registry where\n registry.path : \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\Configuration\\\\EnableAt\" and\n registry.data.strings : (\"1\", \"0x00000001\")\n", + "query": "registry where\n registry.path : (\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\Configuration\\\\EnableAt\",\n \"\\\\REGISTRY\\\\MACHINE\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Schedule\\\\Configuration\\\\EnableAt\"\n ) and registry.data.strings : (\"1\", \"0x00000001\")\n", "references": [ "https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-scheduledjob" ], @@ -38,7 +39,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -66,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index e6b382e3e7db7f..3a496167736d1d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -35,7 +36,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -63,5 +65,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 28b77bf1c18ef9..2a19d7284c7dc9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -5,12 +5,13 @@ "description": "Identifies modifications to the registered Subject Interface Package (SIP) providers. SIP providers are used by the Windows cryptographic system to validate file signatures on the system. This may be an attempt to bypass signature validation checks or inject code into critical processes.", "from": "now-9m", "index": [ - "logs-endpoint.events.*" + "logs-endpoint.events.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "SIP Provider Modification", - "query": "registry where event.type:\"change\" and\n registry.path: (\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Cryptography\\\\OID\\\\EncodingType 0\\\\CryptSIPDllPutSignedDataMsg\\\\{*}\\\\Dll\",\n \"HKLM\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Cryptography\\\\OID\\\\EncodingType 0\\\\CryptSIPDllPutSignedDataMsg\\\\{*}\\\\Dll\",\n \"HKLM\\\\SOFTWARE\\\\Microsoft\\\\Cryptography\\\\Providers\\\\Trust\\\\FinalPolicy\\\\{*}\\\\$Dll\",\n \"HKLM\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Cryptography\\\\Providers\\\\Trust\\\\FinalPolicy\\\\{*}\\\\$Dll\"\n ) and\n registry.data.strings:\"*.dll\"\n", + "query": "registry where event.type:\"change\" and\n registry.path: (\n \"*\\\\SOFTWARE\\\\Microsoft\\\\Cryptography\\\\OID\\\\EncodingType 0\\\\CryptSIPDllPutSignedDataMsg\\\\{*}\\\\Dll\",\n \"*\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Cryptography\\\\OID\\\\EncodingType 0\\\\CryptSIPDllPutSignedDataMsg\\\\{*}\\\\Dll\",\n \"*\\\\SOFTWARE\\\\Microsoft\\\\Cryptography\\\\Providers\\\\Trust\\\\FinalPolicy\\\\{*}\\\\$Dll\",\n \"*\\\\SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Cryptography\\\\Providers\\\\Trust\\\\FinalPolicy\\\\{*}\\\\$Dll\"\n ) and\n registry.data.strings:\"*.dll\"\n", "references": [ "https://github.com/mattifestation/PoCSubjectInterfacePackage" ], @@ -39,7 +40,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -67,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index f555d11865315b..0812ce63e357e5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,13 +7,14 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "SolarWinds Process Disabling Services via Registry", "note": "", - "query": "registry where registry.path : \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\*\\\\Start\" and\n registry.data.strings : (\"4\", \"0x00000004\") and\n process.name : (\n \"SolarWinds.BusinessLayerHost*.exe\",\n \"ConfigurationWizard*.exe\",\n \"NetflowDatabaseMaintenance*.exe\",\n \"NetFlowService*.exe\",\n \"SolarWinds.Administration*.exe\",\n \"SolarWinds.Collector.Service*.exe\" ,\n \"SolarwindsDiagnostics*.exe\")\n", + "query": "registry where registry.path : (\n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\*\\\\Start\",\n \"\\\\REGISTRY\\\\MACHINE\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\*\\\\Start\"\n ) and\n registry.data.strings : (\"4\", \"0x00000004\") and\n process.name : (\n \"SolarWinds.BusinessLayerHost*.exe\",\n \"ConfigurationWizard*.exe\",\n \"NetflowDatabaseMaintenance*.exe\",\n \"NetFlowService*.exe\",\n \"SolarWinds.Administration*.exe\",\n \"SolarWinds.Collector.Service*.exe\",\n \"SolarwindsDiagnostics*.exe\")\n", "references": [ "https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html" ], @@ -43,7 +44,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -93,5 +95,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c5dab826da0f81..7d2fc7ef64a0f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -8,7 +8,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -52,7 +53,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -75,5 +77,5 @@ "timeline_title": "Comprehensive Process Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/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 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 index f537c79c427e5a..29979fd48a6ed5 100644 --- a/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 +++ 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 @@ -20,7 +20,8 @@ "query": "event.dataset:okta.system and\n event.action:(system.email.account_unlock.sent_message or system.email.password_reset.sent_message or\n system.sms.send_account_unlock_message or system.sms.send_password_reset_message or\n system.voice.send_account_unlock_call or system.voice.send_password_reset_call or\n user.account.unlock_token)\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -106,5 +107,5 @@ "value": 5 }, "type": "threshold", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 71090cbe65b647..d565b015ad9b4a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -73,7 +73,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -93,5 +94,5 @@ } ], "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index d83fd0fff02268..e1d70d4760d2ff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -45,7 +46,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -73,5 +75,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index e8afca5d92c1ff..1aff739a22cbc5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -66,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 2e44936da71b22..d1ee84ce333052 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -41,7 +42,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -62,5 +64,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index e9d8c440038b77..b613718047b81f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "has_guide" ], "threat": [ { @@ -73,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a6397718b59538..3cb225fc8453ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -68,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index f52c898b0b316d..4adc26944dc65f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -61,5 +63,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 217e400e8221a2..45af4f583f286e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Defense Evasion" + "Defense Evasion", + "Elastic Endgame" ], "threat": [ { @@ -68,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index aeaba22f4b2816..d645fd560ee770 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "from": "now-9m", "index": [ "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -51,7 +52,8 @@ "Windows", "Threat Detection", "Defense Evasion", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -72,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index fa27159b49fd1c..a0112215928b09 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -54,7 +55,8 @@ "Windows", "Threat Detection", "Discovery", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -104,5 +106,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 76d95e51f4692a..58406e03f64e78 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -51,7 +52,8 @@ "Windows", "Threat Detection", "Discovery", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -84,5 +86,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 966b47558b7f5a..b75b9f01237041 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -47,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "Elastic Endgame" ], "threat": [ { @@ -68,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_files_dir_systeminfo_via_cmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_files_dir_systeminfo_via_cmd.json new file mode 100644 index 00000000000000..c5c7a81c2be885 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_files_dir_systeminfo_via_cmd.json @@ -0,0 +1,93 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies the execution of discovery commands to enumerate system info or files and folders using the Windows Command Shell.", + "from": "now-9m", + "index": [ + "winlogbeat-*", + "logs-windows.*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "System Information Discovery via Windows Command Shell", + "note": "", + "query": "process where event.type == \"start\" and\n process.name : \"cmd.exe\" and process.args : \"/c\" and process.args : (\"set\", \"dir\")\n", + "required_fields": [ + { + "ecs": true, + "name": "event.type", + "type": "keyword" + }, + { + "ecs": true, + "name": "process.args", + "type": "keyword" + }, + { + "ecs": true, + "name": "process.name", + "type": "keyword" + } + ], + "risk_score": 21, + "rule_id": "d68e95ad-1c82-4074-a12a-125fe10ac8ba", + "setup": "If enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.", + "severity": "low", + "tags": [ + "Elastic", + "Host", + "Windows", + "Threat Detection", + "Discovery", + "Execution" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0007", + "name": "Discovery", + "reference": "https://attack.mitre.org/tactics/TA0007/" + }, + "technique": [ + { + "id": "T1082", + "name": "System Information Discovery", + "reference": "https://attack.mitre.org/techniques/T1082/" + }, + { + "id": "T1083", + "name": "File and Directory Discovery", + "reference": "https://attack.mitre.org/techniques/T1083/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.003", + "name": "Windows Command Shell", + "reference": "https://attack.mitre.org/techniques/T1059/003/" + } + ] + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 38779d72a19d42..50e3f7b9dd824e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -51,7 +52,8 @@ "Windows", "Threat Detection", "Discovery", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -77,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 08d44e6911cae8..3c8fece10bf16d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -46,7 +47,8 @@ "Windows", "Threat Detection", "Discovery", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -67,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index cd5b4ebbdf9f20..08f7141ba51bb6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -39,7 +39,8 @@ "Host", "Windows", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { @@ -87,5 +88,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index e0bf3a485e8353..8a9dae698ee1de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -41,7 +42,8 @@ "Windows", "Threat Detection", "Discovery", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -67,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index dbed80d94bef88..06051db044b0ad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -53,7 +53,8 @@ "macOS", "Linux", "Threat Detection", - "Discovery" + "Discovery", + "has_guide" ], "threat": [ { @@ -81,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 5e0d757a0c2327..2bd143b5eb81fb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -46,7 +47,8 @@ "Windows", "Threat Detection", "Discovery", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -74,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 57c608717970d4..cce2c43be7f067 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -13,13 +13,29 @@ "license": "Elastic License v2", "name": "Kubernetes Suspicious Self-Subject Review", "note": "", - "query": "kubernetes.audit.verb:\"create\"\nand kubernetes.audit.objectRef.resource:(\"selfsubjectaccessreviews\" or \"selfsubjectrulesreviews\")\nand kubernetes.audit.user.username:(system\\:serviceaccount\\:* or system\\:node\\:*) or kubernetes.audit.impersonatedUser.username:(system\\:serviceaccount\\:* or system\\:node\\:*)\n", + "query": "event.dataset : \"kubernetes.audit_logs\" \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\"\n and kubernetes.audit.verb:\"create\"\n and kubernetes.audit.objectRef.resource:(\"selfsubjectaccessreviews\" or \"selfsubjectrulesreviews\")\n and (kubernetes.audit.user.username:(system\\:serviceaccount\\:* or system\\:node\\:*) \n or kubernetes.audit.impersonatedUser.username:(system\\:serviceaccount\\:* or system\\:node\\:*))\n", "references": [ "https://www.paloaltonetworks.com/apps/pan/public/downloadResource?pagePath=/content/pan/en_US/resources/whitepapers/kubernetes-privilege-escalation-excessive-permissions-in-popular-platforms", "https://kubernetes.io/docs/reference/access-authn-authz/authorization/#checking-api-access", "https://techcommunity.microsoft.com/t5/microsoft-defender-for-cloud/detecting-identity-attacks-in-kubernetes/ba-p/3232340" ], + "related_integrations": [ + { + "package": "kubernetes", + "version": "^1.4.1" + } + ], "required_fields": [ + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": false, + "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.impersonatedUser.username", @@ -70,5 +86,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index f27272de5cee04..999f9c5468e3ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -11,7 +11,8 @@ "winlogbeat-*", "logs-endpoint.events.*", "logs-windows.*", - "logs-system.*" + "logs-system.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -65,7 +66,8 @@ "Windows", "Threat Detection", "Discovery", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -86,5 +88,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index f5b8a199598392..7ce38535022d3c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -12,7 +12,7 @@ "license": "Elastic License v2", "max_signals": 10000, "name": "Adversary Behavior - Detected - Elastic Endgame", - "query": "event.kind:alert and event.module:endgame and (event.action:rules_engine_event or endgame.event_subtype_full:rules_engine_event)\n", + "query": "event.kind:alert and event.module:endgame and (event.action:behavior_protection_event or endgame.event_subtype_full:behavior_protection_event)\n", "required_fields": [ { "ecs": false, @@ -43,5 +43,5 @@ "Elastic Endgame" ], "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 785c950719b8f1..7766b13dc2ca2c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,11 +14,12 @@ "license": "Elastic License v2", "name": "Abnormal Process ID or Lock File Created", "note": "## Triage and analysis\n\n### Investigating Abnormal Process ID or Lock File Created\n\nLinux applications may need to save their process identification number (PID) for various purposes: from signaling that\na program is running to serving as a signal that a previous instance of an application didn't exit successfully. PID\nfiles contain its creator process PID in an integer value.\n\nLinux lock files are used to coordinate operations in files so that conflicts and race conditions are prevented.\n\nThis rule identifies the creation of PID, lock, or reboot files in the /var/run/ directory. Attackers can masquerade\nmalware, payloads, staged data for exfiltration, and more as legitimate PID files.\n\n#### Possible investigation steps\n\n- Retrieve the file and determine if it is malicious:\n - Check the contents of the PID files. They should only contain integer strings.\n - Check the file type of the lock and PID files to determine if they are executables. This is only observed in\n malicious files.\n - Check the size of the subject file. Legitimate PID files should be under 10 bytes.\n - Check if the lock or PID file has high entropy. This typically indicates an encrypted payload.\n - Analysts can use tools like `ent` to measure entropy.\n - Examine the reputation of the SHA-256 hash in the PID file. Use a database like VirusTotal to identify additional\n pivots and artifacts for investigation.\n- Trace the file's creation to ensure it came from a legitimate or authorized process.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n- Investigate any abnormal behavior by the subject process such as network connections, file modifications, and any\nspawned child processes.\n\n### False positive analysis\n\n- False positives can appear if the PID file is legitimate and holding a process ID as intended. If the PID file is\nan executable or has a file size that's larger than 10 bytes, it should be ruled suspicious.\n- If this activity is expected and noisy in your environment, consider adding exceptions \u2014 preferably with a combination\nof file name and process executable conditions.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Block the identified indicators of compromise (IoCs).\n- Take actions to terminate processes and connections used by the attacker.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", - "query": "/* add file size filters when data is available */\nfile where event.type == \"creation\" and user.id == \"0\" and\n file.path regex~ \"\"\"/var/run/\\w+\\.(pid|lock|reboot)\"\"\" and file.extension in (\"pid\",\"lock\",\"reboot\") and\n\n /* handle common legitimate files */\n\n not file.name in (\n \"auditd.pid\",\n \"python*\",\n \"apport.pid\",\n \"apport.lock\",\n \"kworker*\",\n \"gdm3.pid\",\n \"sshd.pid\",\n \"acpid.pid\",\n \"unattended-upgrades.lock\",\n \"unattended-upgrades.pid\",\n \"cmd.pid\",\n \"cron*.pid\",\n \"yum.pid\",\n \"netconfig.pid\",\n \"docker.pid\",\n \"atd.pid\",\n \"lfd.pid\",\n \"atop.pid\",\n \"nginx.pid\",\n \"dhclient.pid\",\n \"smtpd.pid\",\n \"stunnel.pid\"\n )\n", + "query": "/* add file size filters when data is available */\nfile where event.type == \"creation\" and user.id == \"0\" and\n file.path regex~ \"\"\"/var/run/\\w+\\.(pid|lock|reboot)\"\"\" and file.extension in (\"pid\",\"lock\",\"reboot\") and\n\n /* handle common legitimate files */\n\n not file.name in (\n \"auditd.pid\",\n \"python*\",\n \"apport.pid\",\n \"apport.lock\",\n \"kworker*\",\n \"gdm3.pid\",\n \"sshd.pid\",\n \"acpid.pid\",\n \"unattended-upgrades.lock\",\n \"unattended-upgrades.pid\",\n \"cmd.pid\",\n \"cron*.pid\",\n \"yum.pid\",\n \"netconfig.pid\",\n \"docker.pid\",\n \"atd.pid\",\n \"lfd.pid\",\n \"atop.pid\",\n \"nginx.pid\",\n \"dhclient.pid\",\n \"smtpd.pid\",\n \"stunnel.pid\",\n \"1_waagent.pid\"\n )\n", "references": [ "https://www.sandflysecurity.com/blog/linux-file-masquerading-and-malicious-pids-sandfly-1-2-6-update/", "https://twitter.com/GossiTheDog/status/1522964028284411907", - "https://exatrack.com/public/Tricephalic_Hellkeeper.pdf" + "https://exatrack.com/public/Tricephalic_Hellkeeper.pdf", + "https://www.elastic.co/security-labs/a-peek-behind-the-bpfdoor" ], "required_fields": [ { @@ -56,7 +57,8 @@ "Linux", "Threat Detection", "Execution", - "BPFDoor" + "BPFDoor", + "has_guide" ], "threat": [ { @@ -77,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 34d27330d75224..423b8470178819 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -47,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -90,5 +92,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 89818d4b601d2c..b4818319f7bbb0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -52,7 +53,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -95,5 +97,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index d151424a662fb9..03ed2491bcd6f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -49,7 +50,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -77,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 7cea863e990a27..2328a51a411120 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -54,7 +55,8 @@ "Windows", "Threat Detection", "Execution", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -77,5 +79,5 @@ "timeline_title": "Comprehensive Process Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 736bb2a6d3c7ac..b5b9c575700230 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -61,5 +63,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 4a293f4b144426..f2a16f04b244f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -53,7 +54,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -81,5 +83,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index b7d52e0c7960c6..c22fd8c13730f5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -86,5 +88,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_linux_netcat_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_file_transfer_or_listener_established_via_netcat.json similarity index 74% rename from x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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_file_transfer_or_listener_established_via_netcat.json index ce07c4a04bfca8..328c49f6b22f8f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_linux_netcat_network_connection.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_file_transfer_or_listener_established_via_netcat.json @@ -13,18 +13,21 @@ ], "language": "eql", "license": "Elastic License v2", - "name": "Netcat Network Activity", + "name": "File Transfer or Listener Established via Netcat", "note": "## Triage and analysis\n\n### Investigating Netcat Network Activity\n\nNetcat is a dual-use command line tool that can be used for various purposes, such as port scanning, file transfers, and\nconnection tests. Attackers can abuse its functionality for malicious purposes such creating bind shells or reverse\nshells to gain access to the target system.\n\nA reverse shell is a mechanism that's abused to connect back to an attacker-controlled system. It effectively redirects\nthe system's input and output and delivers a fully functional remote shell to the attacker. Even private systems are\nvulnerable since the connection is outgoing.\n\nA bind shell is a type of backdoor that attackers set up on the target host and binds to a specific port to listen for\nan incoming connection from the attacker.\n\nThis rule identifies potential reverse shell or bind shell activity using Netcat by checking for the execution of Netcat\nfollowed by a network connection.\n\n#### Possible investigation steps\n\n- Examine the command line to identify if the command is suspicious.\n- Extract and examine the target domain or IP address.\n - Check if the domain is newly registered or unexpected.\n - Check the reputation of the domain or IP address.\n - Scope other potentially compromised hosts in your environment by mapping hosts that also communicated with the\n domain or IP address.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Investigate any abnormal account behavior, such as command executions, file creations or modifications, and network\nconnections.\n- Investigate any abnormal behavior by the subject process such as network connections, file modifications, and any\nspawned child processes.\n\n### False positive analysis\n\n- Netcat is a dual-use tool that can be used for benign or malicious activity. It is included in some Linux\ndistributions, so its presence is not necessarily suspicious. Some normal use of this program, while uncommon, may\noriginate from scripts, automation tools, and frameworks.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Block the identified indicators of compromise (IoCs).\n- Take actions to terminate processes and connections used by the attacker.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", - "query": "sequence by process.entity_id\n [process where (process.name == \"nc\" or process.name == \"ncat\" or process.name == \"netcat\" or\n process.name == \"netcat.openbsd\" or process.name == \"netcat.traditional\") and\n event.type == \"start\"]\n [network where (process.name == \"nc\" or process.name == \"ncat\" or process.name == \"netcat\" or\n process.name == \"netcat.openbsd\" or process.name == \"netcat.traditional\")]\n", + "query": "sequence by process.entity_id\n [process where process.name:(\"nc\",\"ncat\",\"netcat\",\"netcat.openbsd\",\"netcat.traditional\") and (\n /* bind shell to echo for command execution */\n (process.args:(\"-l\",\"-p\") and process.args:(\"-c\",\"echo\",\"$*\"))\n /* bind shell to specific port */\n or process.args:(\"-l\",\"-p\",\"-lp\")\n /* reverse shell to command-line interpreter used for command execution */\n or (process.args:(\"-e\") and process.args:(\"/bin/bash\",\"/bin/sh\"))\n /* file transfer via stdout */\n or process.args:(\">\",\"<\")\n /* file transfer via pipe */\n or (process.args:(\"|\") and process.args:(\"nc\",\"ncat\"))\n )]\n [network where (process.name == \"nc\" or process.name == \"ncat\" or process.name == \"netcat\" or\n process.name == \"netcat.openbsd\" or process.name == \"netcat.traditional\")]\n", "references": [ "http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet", "https://www.sans.org/security-resources/sec560/netcat_cheat_sheet_v1.pdf", - "https://en.wikipedia.org/wiki/Netcat" + "https://en.wikipedia.org/wiki/Netcat", + "https://www.hackers-arise.com/hacking-fundamentals", + "https://null-byte.wonderhowto.com/how-to/hack-like-pro-use-netcat-swiss-army-knife-hacking-tools-0148657/", + "https://levelup.gitconnected.com/ethical-hacking-part-15-netcat-nc-and-netcat-f6a8f7df43fd" ], "required_fields": [ { "ecs": true, - "name": "event.type", + "name": "process.args", "type": "keyword" }, { @@ -46,7 +49,8 @@ "Host", "Linux", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { @@ -60,11 +64,18 @@ { "id": "T1059", "name": "Command and Scripting Interpreter", - "reference": "https://attack.mitre.org/techniques/T1059/" + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.004", + "name": "Unix Shell", + "reference": "https://attack.mitre.org/techniques/T1059/004/" + } + ] } ] } ], "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 2221085a9b518d..92b534979f1e0d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -46,7 +47,9 @@ "Windows", "Threat Detection", "Execution", - "Defense Evasion" + "Defense Evasion", + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -89,5 +92,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 4b4176e8ebc6df..a770a6109037f0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -15,7 +15,8 @@ ], "name": "Suspicious Powershell Script", "references": [ - "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html" + "https://www.elastic.co/guide/en/security/current/prebuilt-ml-jobs.html", + "https://www.elastic.co/security-labs/detecting-living-off-the-land-attacks-with-new-elastic-integration" ], "risk_score": 21, "rule_id": "1781d055-5c66-4adf-9d60-fc0fa58337b6", @@ -53,5 +54,5 @@ } ], "type": "machine_learning", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 89f6364c7571cf..60af2ecac4d6c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "interval": "60m", "language": "eql", @@ -56,7 +57,8 @@ "Windows", "Threat Detection", "Execution", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -97,5 +99,5 @@ } ], "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index e525b02a6efd54..afba98240b949d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "interval": "60m", "language": "eql", @@ -61,7 +62,8 @@ "Windows", "Threat Detection", "Execution", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -102,5 +104,5 @@ } ], "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 394c8b047a372f..bba60430286a47 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://www.sandflysecurity.com/blog/linux-file-masquerading-and-malicious-pids-sandfly-1-2-6-update/", "https://twitter.com/GossiTheDog/status/1522964028284411907", - "https://exatrack.com/public/Tricephalic_Hellkeeper.pdf" + "https://exatrack.com/public/Tricephalic_Hellkeeper.pdf", + "https://www.elastic.co/security-labs/a-peek-behind-the-bpfdoor" ], "required_fields": [ { @@ -67,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a2562256f209bb..5d203951f3cf47 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -16,7 +16,8 @@ "query": "process where event.type == \"start\" and\n event.action == \"exec\" and user.name == \"root\" and\n process.executable : (\n \"/dev/shm/*\",\n \"/run/shm/*\",\n \"/var/run/*\",\n \"/var/lock/*\"\n ) and\n not process.executable : ( \"/var/run/docker/*\")\n", "references": [ "https://linuxsecurity.com/features/fileless-malware-on-linux", - "https://twitter.com/GossiTheDog/status/1522964028284411907" + "https://twitter.com/GossiTheDog/status/1522964028284411907", + "https://www.elastic.co/security-labs/a-peek-behind-the-bpfdoor" ], "required_fields": [ { @@ -70,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_reverse_shell_via_named_pipe.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_reverse_shell_via_named_pipe.json new file mode 100644 index 00000000000000..10a563f0f74b90 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_reverse_shell_via_named_pipe.json @@ -0,0 +1,87 @@ +{ + "author": [ + "Elastic" + ], + "description": "Identifies a reverse shell via the abuse of named pipes on Linux with the help of OpenSSL or Netcat. First in, first out (FIFO) files are special files for reading and writing to by Linux processes. For this to work, a named pipe is created and passed to a Linux shell where the use of a network connection tool such as Netcat or OpenSSL has been established. The stdout and stderr are captured in the named pipe from the network connection and passed back to the shell for execution.", + "false_positives": [ + "Netcat and OpenSSL are common tools used for establishing network connections and creating encryption keys. While they are popular, capturing the stdout and stderr in a named pipe pointed to a shell is anomalous." + ], + "from": "now-9m", + "index": [ + "auditbeat-*", + "logs-endpoint.events.*" + ], + "language": "eql", + "license": "Elastic License v2", + "name": "Reverse Shell Created via Named Pipe", + "query": "sequence by host.id with maxspan = 5s\n [process where event.type == \"start\" and process.executable : (\"/usr/bin/mkfifo\",\"/usr/bin/mknod\") and process.args:(\"/tmp/*\",\"$*\")]\n [process where process.executable : (\"/bin/sh\",\"/bin/bash\") and process.args:(\"-i\") or\n (process.executable: (\"/usr/bin/openssl\") and process.args: (\"-connect\"))]\n [process where (process.name:(\"nc\",\"ncat\",\"netcat\",\"netcat.openbsd\",\"netcat.traditional\") or\n (process.name: \"openssl\" and process.executable: \"/usr/bin/openssl\"))]\n", + "references": [ + "https://int0x33.medium.com/day-43-reverse-shell-with-openssl-1ee2574aa998", + "https://blog.gregscharf.com/2021/03/22/tar-in-cronjob-to-privilege-escalation/", + "https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md#openssl" + ], + "required_fields": [ + { + "ecs": true, + "name": "event.type", + "type": "keyword" + }, + { + "ecs": true, + "name": "host.id", + "type": "keyword" + }, + { + "ecs": true, + "name": "process.args", + "type": "keyword" + }, + { + "ecs": true, + "name": "process.executable", + "type": "keyword" + }, + { + "ecs": true, + "name": "process.name", + "type": "keyword" + } + ], + "risk_score": 47, + "rule_id": "dd7f1524-643e-11ed-9e35-f661ea17fbcd", + "severity": "medium", + "tags": [ + "Elastic", + "Host", + "Linux", + "Threat Detection", + "Execution", + "has_guide" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1059", + "name": "Command and Scripting Interpreter", + "reference": "https://attack.mitre.org/techniques/T1059/", + "subtechnique": [ + { + "id": "T1059.004", + "name": "Unix Shell", + "reference": "https://attack.mitre.org/techniques/T1059/004/" + } + ] + } + ] + } + ], + "type": "eql", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index fbefe7152d9c0f..c5587ebff2613c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -65,7 +65,8 @@ "Linux", "macOS", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { @@ -86,5 +87,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index e8338519fe8f8d..6017f4d2460d34 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -38,7 +39,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -59,5 +61,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 0b8153b2ac9d55..8ca50064b05d3a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -45,7 +46,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -66,5 +68,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index dcee904b4ceb10..4a5782fe5e07ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,13 +7,14 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Suspicious WMI Image Load from MS Office", "note": "", - "query": "any where\n (event.category == \"library\" or (event.category == \"process\" and event.action : \"Image loaded*\")) and\n process.name : (\"WINWORD.EXE\", \"EXCEL.EXE\", \"POWERPNT.EXE\", \"MSPUB.EXE\", \"MSACCESS.EXE\") and\n (dll.name : \"wmiutils.dll\" or file.name : \"wmiutils.dll\")\n", + "query": "any where\n (event.category : (\"library\", \"driver\") or (event.category == \"process\" and event.action : \"Image loaded*\")) and\n process.name : (\"WINWORD.EXE\", \"EXCEL.EXE\", \"POWERPNT.EXE\", \"MSPUB.EXE\", \"MSACCESS.EXE\") and\n (dll.name : \"wmiutils.dll\" or file.name : \"wmiutils.dll\")\n", "references": [ "https://medium.com/threatpunter/detecting-adversary-tradecraft-with-image-load-event-logging-and-eql-8de93338c16" ], @@ -53,7 +54,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -74,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 5e742ae4ed4bbb..4913e95055e578 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -16,7 +16,9 @@ "references": [ "https://www.lunasec.io/docs/blog/log4j-zero-day/", "https://github.com/christophetd/log4shell-vulnerable-app", - "https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf" + "https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf", + "https://www.elastic.co/security-labs/detecting-log4j2-with-elastic-security", + "https://www.elastic.co/security-labs/analysis-of-log4shell-cve-2021-45046" ], "required_fields": [ { @@ -45,7 +47,8 @@ "Linux", "macOS", "Threat Detection", - "Execution" + "Execution", + "has_guide" ], "threat": [ { @@ -73,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 6b606117e1cae3..f7abf2ee40ab8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -15,7 +15,9 @@ "references": [ "https://www.lunasec.io/docs/blog/log4j-zero-day/", "https://github.com/christophetd/log4shell-vulnerable-app", - "https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf" + "https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf", + "https://www.elastic.co/security-labs/detecting-log4j2-with-elastic-security", + "https://www.elastic.co/security-labs/analysis-of-log4shell-cve-2021-45046" ], "required_fields": [ { @@ -100,5 +102,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 3eab94d7a63e70..09dd786c19dc12 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -41,7 +42,8 @@ "Windows", "Threat Detection", "Execution", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -62,5 +64,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index b1b2b87bc5d87e..db43407a29b842 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,13 +7,14 @@ "index": [ "logs-endpoint.events.*", "winlogbeat-*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", "name": "Suspicious PowerShell Engine ImageLoad", "note": "## Triage and analysis\n\n### Investigating Suspicious PowerShell Engine ImageLoad\n\nPowerShell is one of the main tools system administrators use for automation, report routines, and other tasks. This\nmakes it available for use in various environments, and creates an attractive way for attackers to execute code.\n\nAttackers can use PowerShell without having to execute `PowerShell.exe` directly. This technique, often called\n\"PowerShell without PowerShell,\" works by using the underlying System.Management.Automation namespace and can bypass\napplication allowlisting and PowerShell security features.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate abnormal behaviors observed by the subject process, such as network connections, registry or file\nmodifications, and any spawned child processes.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Inspect the host for suspicious or abnormal behavior in the alert timeframe.\n- Retrieve the implementation (DLL, executable, etc.) and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell `Get-FileHash` cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity can happen legitimately. Some vendors have their own PowerShell implementations that are shipped with\nsome products. These benign true positives (B-TPs) can be added as exceptions if necessary after analysis.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved hosts to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n\n\n\nIf enabling an EQL rule on a non-elastic-agent index (such as beats) for versions <8.2, events will not define `event.ingested` and default fallback for EQL rules was not added until 8.2, so you will need to add a custom pipeline to populate `event.ingested` to @timestamp for this rule to work.", - "query": "any where (event.category == \"library\" or (event.category == \"process\" and event.action : \"Image loaded*\")) and\n (dll.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\") or\n file.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\")) and\n\n/* add false positives relevant to your environment here */\nnot process.executable : (\"C:\\\\Windows\\\\System32\\\\RemoteFXvGPUDisablement.exe\", \"C:\\\\Windows\\\\System32\\\\sdiagnhost.exe\") and\nnot process.executable regex~ \"\"\"C:\\\\Program Files( \\(x86\\))?\\\\*\\.exe\"\"\" and\n not process.name :\n (\n \"Altaro.SubAgent.exe\",\n \"AppV_Manage.exe\",\n \"azureadconnect.exe\",\n \"CcmExec.exe\",\n \"configsyncrun.exe\",\n \"choco.exe\",\n \"ctxappvservice.exe\",\n \"DVLS.Console.exe\",\n \"edgetransport.exe\",\n \"exsetup.exe\",\n \"forefrontactivedirectoryconnector.exe\",\n \"InstallUtil.exe\",\n \"JenkinsOnDesktop.exe\",\n \"Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe\",\n \"mmc.exe\",\n \"mscorsvw.exe\",\n \"msexchangedelivery.exe\",\n \"msexchangefrontendtransport.exe\",\n \"msexchangehmworker.exe\",\n \"msexchangesubmission.exe\",\n \"msiexec.exe\",\n \"MsiExec.exe\",\n \"noderunner.exe\",\n \"NServiceBus.Host.exe\",\n \"NServiceBus.Host32.exe\",\n \"NServiceBus.Hosting.Azure.HostProcess.exe\",\n \"OuiGui.WPF.exe\",\n \"powershell.exe\",\n \"powershell_ise.exe\",\n \"pwsh.exe\",\n \"SCCMCliCtrWPF.exe\",\n \"ScriptEditor.exe\",\n \"ScriptRunner.exe\",\n \"sdiagnhost.exe\",\n \"servermanager.exe\",\n \"setup100.exe\",\n \"ServiceHub.VSDetouredHost.exe\",\n \"SPCAF.Client.exe\",\n \"SPCAF.SettingsEditor.exe\",\n \"SQLPS.exe\",\n \"telemetryservice.exe\",\n \"UMWorkerProcess.exe\",\n \"w3wp.exe\",\n \"wsmprovhost.exe\"\n )\n", + "query": "any where (event.category : (\"library\", \"driver\") or (event.category == \"process\" and event.action : \"Image loaded*\")) and\n (dll.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\") or\n file.name : (\"System.Management.Automation.ni.dll\", \"System.Management.Automation.dll\")) and\n\n/* add false positives relevant to your environment here */\nnot process.executable : (\"C:\\\\Windows\\\\System32\\\\RemoteFXvGPUDisablement.exe\", \"C:\\\\Windows\\\\System32\\\\sdiagnhost.exe\") and\nnot process.executable regex~ \"\"\"C:\\\\Program Files( \\(x86\\))?\\\\*\\.exe\"\"\" and\n not process.name :\n (\n \"Altaro.SubAgent.exe\",\n \"AppV_Manage.exe\",\n \"azureadconnect.exe\",\n \"CcmExec.exe\",\n \"configsyncrun.exe\",\n \"choco.exe\",\n \"ctxappvservice.exe\",\n \"DVLS.Console.exe\",\n \"edgetransport.exe\",\n \"exsetup.exe\",\n \"forefrontactivedirectoryconnector.exe\",\n \"InstallUtil.exe\",\n \"JenkinsOnDesktop.exe\",\n \"Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe\",\n \"mmc.exe\",\n \"mscorsvw.exe\",\n \"msexchangedelivery.exe\",\n \"msexchangefrontendtransport.exe\",\n \"msexchangehmworker.exe\",\n \"msexchangesubmission.exe\",\n \"msiexec.exe\",\n \"MsiExec.exe\",\n \"noderunner.exe\",\n \"NServiceBus.Host.exe\",\n \"NServiceBus.Host32.exe\",\n \"NServiceBus.Hosting.Azure.HostProcess.exe\",\n \"OuiGui.WPF.exe\",\n \"powershell.exe\",\n \"powershell_ise.exe\",\n \"pwsh.exe\",\n \"SCCMCliCtrWPF.exe\",\n \"ScriptEditor.exe\",\n \"ScriptRunner.exe\",\n \"sdiagnhost.exe\",\n \"servermanager.exe\",\n \"setup100.exe\",\n \"ServiceHub.VSDetouredHost.exe\",\n \"SPCAF.Client.exe\",\n \"SPCAF.SettingsEditor.exe\",\n \"SQLPS.exe\",\n \"telemetryservice.exe\",\n \"UMWorkerProcess.exe\",\n \"w3wp.exe\",\n \"wsmprovhost.exe\"\n )\n", "required_fields": [ { "ecs": true, @@ -56,7 +57,8 @@ "Windows", "Threat Detection", "Execution", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -84,5 +86,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 172c61f684c218..a1c4c8116f92d1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -40,7 +41,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -68,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 53c7ae7cb3fe04..c3b95d43530ff2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -13,12 +13,28 @@ "license": "Elastic License v2", "name": "Kubernetes User Exec into Pod", "note": "", - "query": "kubernetes.audit.objectRef.resource:\"pods\"\n and kubernetes.audit.objectRef.subresource:\"exec\"\n", + "query": "event.dataset : \"kubernetes.audit_logs\" \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\" \n and kubernetes.audit.verb:\"create\" \n and kubernetes.audit.objectRef.resource:\"pods\"\n and kubernetes.audit.objectRef.subresource:\"exec\"\n", "references": [ "https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/", "https://kubernetes.io/docs/tasks/debug/debug-application/get-shell-running-container/" ], + "related_integrations": [ + { + "package": "kubernetes", + "version": "^1.4.1" + } + ], "required_fields": [ + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": false, + "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.objectRef.resource", @@ -28,6 +44,11 @@ "ecs": false, "name": "kubernetes.audit.objectRef.subresource", "type": "unknown" + }, + { + "ecs": false, + "name": "kubernetes.audit.verb", + "type": "unknown" } ], "risk_score": 47, @@ -59,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index bbc808ea6e9250..d77eab3654981b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -43,7 +44,8 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "Elastic Endgame" ], "threat": [ { @@ -93,5 +95,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 873ed4a120f19e..665051435a8a4c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -49,7 +50,8 @@ "Windows", "Threat Detection", "Execution", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -70,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 2b55041c64fe70..bea8f042b1393a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -7,7 +7,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -48,7 +49,9 @@ "Host", "Windows", "Threat Detection", - "Execution" + "Execution", + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -69,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 0c2c25e53bc311..d39b78f58a08da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -17,7 +17,8 @@ "query": "event.dataset:okta.system and event.action:system.api_token.revoke\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 73d7ed7c943bfb..6e7cc80e971ac1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -10,7 +10,8 @@ "index": [ "winlogbeat-*", "logs-endpoint.events.*", - "logs-windows.*" + "logs-windows.*", + "endgame-*" ], "language": "eql", "license": "Elastic License v2", @@ -47,7 +48,8 @@ "Windows", "Threat Detection", "Impact", - "has_guide" + "has_guide", + "Elastic Endgame" ], "threat": [ { @@ -68,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a7ce614def97fb..e93c0f1d9a6334 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -56,7 +56,8 @@ "Windows", "macOS", "Threat Detection", - "Impact" + "Impact", + "has_guide" ], "threat": [ { @@ -86,5 +87,5 @@ "timeline_title": "Comprehensive File Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_kms_cmk_disabled_or_scheduled_for_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_kms_cmk_disabled_or_scheduled_for_deletion.json new file mode 100644 index 00000000000000..a759fc2a22225f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_kms_cmk_disabled_or_scheduled_for_deletion.json @@ -0,0 +1,86 @@ +{ + "author": [ + "Xavier Pich" + ], + "description": "Identifies attempts to disable or schedule the deletion of an AWS KMS Customer Managed Key (CMK). Deleting an AWS KMS key is destructive and potentially dangerous. It deletes the key material and all metadata associated with the KMS key and is irreversible. After a KMS key is deleted, the data that was encrypted under that KMS key can no longer be decrypted, which means that data becomes unrecoverable.", + "false_positives": [ + "A KMS customer managed key may be disabled or scheduled for deletion by a system administrator. Verify whether the user identity, user agent, and/or hostname should be making changes in your environment. Key deletions by unfamiliar users should be investigated. If known behavior is causing false positives, it can be exempted from the rule." + ], + "from": "now-60m", + "index": [ + "filebeat-*", + "logs-aws*" + ], + "interval": "10m", + "language": "kuery", + "license": "Elastic License v2", + "name": "AWS KMS Customer Managed Key Disabled or Scheduled for Deletion", + "note": "", + "query": "event.dataset:aws.cloudtrail and event.provider:kms.amazonaws.com and event.action:(\"DisableKey\" or \"ScheduleKeyDeletion\") and event.outcome:success\n", + "references": [ + "https://docs.aws.amazon.com/cli/latest/reference/kms/disable-key.html", + "https://docs.aws.amazon.com/cli/latest/reference/kms/schedule-key-deletion.html" + ], + "related_integrations": [ + { + "integration": "cloudtrail", + "package": "aws", + "version": "^1.5.0" + } + ], + "required_fields": [ + { + "ecs": true, + "name": "event.action", + "type": "keyword" + }, + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": true, + "name": "event.outcome", + "type": "keyword" + }, + { + "ecs": true, + "name": "event.provider", + "type": "keyword" + } + ], + "risk_score": 47, + "rule_id": "6951f15e-533c-4a60-8014-a3c3ab851a1b", + "setup": "The AWS Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", + "severity": "medium", + "tags": [ + "Elastic", + "Cloud", + "AWS", + "Continuous Monitoring", + "SecOps", + "Log Auditing", + "Impact" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0040", + "name": "Impact", + "reference": "https://attack.mitre.org/tactics/TA0040/" + }, + "technique": [ + { + "id": "T1485", + "name": "Data Destruction", + "reference": "https://attack.mitre.org/techniques/T1485/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 790e19aaf182c1..b4a6eca6926664 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Apps/Apps_Apps.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -70,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c816403377b76d..5a0bca5f20d4cd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -17,7 +17,8 @@ "query": "event.dataset:okta.system and event.action:application.lifecycle.delete\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -69,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c6fdccd640393e..3e5441528223dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Apps/Apps_Apps.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -64,5 +65,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 7fc0083fc13c69..09a8f86170f5ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,7 +14,8 @@ "query": "event.dataset:okta.system and event.action:(application.integration.rate_limit_exceeded or system.org.rate_limit.warning or system.org.rate_limit.violation or core.concurrency.org.limit.violation)\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -70,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 8204204bb34c0a..269da717bb7a57 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,6 +14,9 @@ "name": "High Number of Process and/or Service Terminations", "note": "## Triage and analysis\n\n### Investigating High Number of Process and/or Service Terminations\n\nAttackers can stop services and kill processes for a variety of purposes. For example, they can stop services associated\nwith business applications and databases to release the lock on files used by these applications so they may be encrypted,\nor stop security and backup solutions, etc.\n\nThis rule identifies a high number (10) of service and/or process terminations (stop, delete, or suspend) from the same\nhost within a short time period.\n\n#### Possible investigation steps\n\n- Investigate the script execution chain (parent process tree) for unknown processes. Examine their executable files for\nprevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Check if any files on the host machine have been encrypted.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further destructive behavior, which is commonly associated with this activity.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Reimage the host operating system or restore it to the operational state.\n- If any other destructive action was identified on the host, it is recommended to prioritize the investigation and look\nfor ransomware preparation and execution activities.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "event.category:process and event.type:start and process.name:(net.exe or sc.exe or taskkill.exe) and\n process.args:(stop or pause or delete or \"/PID\" or \"/IM\" or \"/T\" or \"/F\" or \"/t\" or \"/f\" or \"/im\" or \"/pid\")\n", + "references": [ + "https://www.elastic.co/security-labs/luna-ransomware-attack-pattern" + ], "required_fields": [ { "ecs": true, @@ -71,5 +74,5 @@ "value": 10 }, "type": "threshold", - "version": 101 + "version": 102 } 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 index 5d2cbf2aa963f5..c3aa1d5f51a10d 100644 --- 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 @@ -10,713 +10,719 @@ // - 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'; +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_accepted_default_telnet_port_connection.json'; +import rule22 from './command_and_control_linux_iodine_activity.json'; +import rule23 from './command_and_control_nat_traversal_port_activity.json'; +import rule24 from './command_and_control_port_26_activity.json'; +import rule25 from './command_and_control_rdp_remote_desktop_protocol_from_the_internet.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_file_transfer_or_listener_established_via_netcat.json'; +import rule53 from './execution_html_help_executable_program_connecting_to_the_internet.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 './credential_access_ml_auth_spike_in_failed_logon_events.json'; +import rule550 from './credential_access_ml_auth_spike_in_logon_events.json'; +import rule551 from './credential_access_ml_auth_spike_in_logon_events_from_a_source_ip.json'; +import rule552 from './initial_access_ml_auth_rare_hour_for_a_user_to_logon.json'; +import rule553 from './initial_access_ml_auth_rare_source_ip_for_a_user.json'; +import rule554 from './initial_access_ml_auth_rare_user_logon.json'; +import rule555 from './privilege_escalation_cyberarkpas_error_audit_event_promotion.json'; +import rule556 from './privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json'; +import rule557 from './defense_evasion_kubernetes_events_deleted.json'; +import rule558 from './impact_kubernetes_pod_deleted.json'; +import rule559 from './exfiltration_rds_snapshot_restored.json'; +import rule560 from './privilege_escalation_printspooler_suspicious_file_deletion.json'; +import rule561 from './privilege_escalation_unusual_printspooler_childprocess.json'; +import rule562 from './defense_evasion_disabling_windows_defender_powershell.json'; +import rule563 from './defense_evasion_enable_network_discovery_with_netsh.json'; +import rule564 from './defense_evasion_execution_windefend_unusual_path.json'; +import rule565 from './defense_evasion_agent_spoofing_mismatched_id.json'; +import rule566 from './defense_evasion_agent_spoofing_multiple_hosts.json'; +import rule567 from './defense_evasion_parent_process_pid_spoofing.json'; +import rule568 from './impact_microsoft_365_potential_ransomware_activity.json'; +import rule569 from './impact_microsoft_365_unusual_volume_of_file_deletion.json'; +import rule570 from './initial_access_microsoft_365_user_restricted_from_sending_email.json'; +import rule571 from './defense_evasion_elasticache_security_group_creation.json'; +import rule572 from './defense_evasion_elasticache_security_group_modified_or_deleted.json'; +import rule573 from './impact_volume_shadow_copy_deletion_via_powershell.json'; +import rule574 from './persistence_route_53_hosted_zone_associated_with_a_vpc.json'; +import rule575 from './defense_evasion_defender_exclusion_via_powershell.json'; +import rule576 from './defense_evasion_dns_over_https_enabled.json'; +import rule577 from './defense_evasion_frontdoor_firewall_policy_deletion.json'; +import rule578 from './credential_access_azure_full_network_packet_capture_detected.json'; +import rule579 from './persistence_webshell_detection.json'; +import rule580 from './defense_evasion_suppression_rule_created.json'; +import rule581 from './impact_efs_filesystem_or_mount_deleted.json'; +import rule582 from './defense_evasion_execution_control_panel_suspicious_args.json'; +import rule583 from './defense_evasion_azure_blob_permissions_modified.json'; +import rule584 from './privilege_escalation_aws_suspicious_saml_activity.json'; +import rule585 from './credential_access_potential_lsa_memdump_via_mirrordump.json'; +import rule586 from './discovery_virtual_machine_fingerprinting_grep.json'; +import rule587 from './impact_backup_file_deletion.json'; +import rule588 from './credential_access_posh_minidump.json'; +import rule589 from './persistence_screensaver_engine_unexpected_child_process.json'; +import rule590 from './persistence_screensaver_plist_file_modification.json'; +import rule591 from './credential_access_suspicious_lsass_access_memdump.json'; +import rule592 from './defense_evasion_suspicious_process_access_direct_syscall.json'; +import rule593 from './discovery_posh_suspicious_api_functions.json'; +import rule594 from './privilege_escalation_via_rogue_named_pipe.json'; +import rule595 from './credential_access_suspicious_lsass_access_via_snapshot.json'; +import rule596 from './defense_evasion_posh_process_injection.json'; +import rule597 from './collection_posh_keylogger.json'; +import rule598 from './defense_evasion_posh_assembly_load.json'; +import rule599 from './defense_evasion_powershell_windows_firewall_disabled.json'; +import rule600 from './execution_posh_portable_executable.json'; +import rule601 from './execution_posh_psreflect.json'; +import rule602 from './credential_access_suspicious_comsvcs_imageload.json'; +import rule603 from './impact_aws_eventbridge_rule_disabled_or_deleted.json'; +import rule604 from './defense_evasion_microsoft_defender_tampering.json'; +import rule605 from './initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json'; +import rule606 from './persistence_remote_password_reset.json'; +import rule607 from './privilege_escalation_azure_kubernetes_rolebinding_created.json'; +import rule608 from './collection_posh_audio_capture.json'; +import rule609 from './collection_posh_screen_grabber.json'; +import rule610 from './defense_evasion_posh_compressed.json'; +import rule611 from './defense_evasion_suspicious_process_creation_calltrace.json'; +import rule612 from './privilege_escalation_group_policy_iniscript.json'; +import rule613 from './privilege_escalation_group_policy_privileged_groups.json'; +import rule614 from './privilege_escalation_group_policy_scheduled_task.json'; +import rule615 from './defense_evasion_clearing_windows_console_history.json'; +import rule616 from './threat_intel_filebeat8x.json'; +import rule617 from './privilege_escalation_installertakeover.json'; +import rule618 from './credential_access_via_snapshot_lsass_clone_creation.json'; +import rule619 from './persistence_via_bits_job_notify_command.json'; +import rule620 from './execution_suspicious_java_netcon_childproc.json'; +import rule621 from './privilege_escalation_samaccountname_spoofing_attack.json'; +import rule622 from './credential_access_symbolic_link_to_shadow_copy_created.json'; +import rule623 from './credential_access_mfa_push_brute_force.json'; +import rule624 from './persistence_azure_global_administrator_role_assigned.json'; +import rule625 from './persistence_microsoft_365_global_administrator_role_assign.json'; +import rule626 from './lateral_movement_malware_uploaded_onedrive.json'; +import rule627 from './lateral_movement_malware_uploaded_sharepoint.json'; +import rule628 from './defense_evasion_ms_office_suspicious_regmod.json'; +import rule629 from './initial_access_o365_user_reported_phish_malware.json'; +import rule630 from './defense_evasion_microsoft_365_mailboxauditbypassassociation.json'; +import rule631 from './credential_access_disable_kerberos_preauth.json'; +import rule632 from './credential_access_posh_request_ticket.json'; +import rule633 from './credential_access_shadow_credentials.json'; +import rule634 from './privilege_escalation_pkexec_envar_hijack.json'; +import rule635 from './credential_access_seenabledelegationprivilege_assigned_to_user.json'; +import rule636 from './persistence_msds_alloweddelegateto_krbtgt.json'; +import rule637 from './defense_evasion_disable_posh_scriptblocklogging.json'; +import rule638 from './persistence_ad_adminsdholder.json'; +import rule639 from './privilege_escalation_windows_service_via_unusual_client.json'; +import rule640 from './credential_access_dcsync_replication_rights.json'; +import rule641 from './credential_access_lsass_memdump_handle_access.json'; +import rule642 from './credential_access_moving_registry_hive_via_smb.json'; +import rule643 from './credential_access_suspicious_winreg_access_via_sebackup_priv.json'; +import rule644 from './credential_access_spn_attribute_modified.json'; +import rule645 from './persistence_dontexpirepasswd_account.json'; +import rule646 from './persistence_sdprop_exclusion_dsheuristics.json'; +import rule647 from './credential_access_remote_sam_secretsdump.json'; +import rule648 from './defense_evasion_workfolders_control_execution.json'; +import rule649 from './credential_access_user_impersonation_access.json'; +import rule650 from './persistence_redshift_instance_creation.json'; +import rule651 from './persistence_crontab_creation.json'; +import rule652 from './privilege_escalation_krbrelayup_service_creation.json'; +import rule653 from './credential_access_relay_ntlm_auth_via_http_spoolss.json'; +import rule654 from './execution_shell_evasion_linux_binary.json'; +import rule655 from './execution_process_started_in_shared_memory_directory.json'; +import rule656 from './execution_abnormal_process_id_file_created.json'; +import rule657 from './execution_process_started_from_process_id_file.json'; +import rule658 from './privilege_escalation_suspicious_dnshostname_update.json'; +import rule659 from './command_and_control_connection_attempt_by_non_ssh_root_session.json'; +import rule660 from './execution_user_exec_to_pod.json'; +import rule661 from './defense_evasion_elastic_agent_service_terminated.json'; +import rule662 from './defense_evasion_proxy_execution_via_msdt.json'; +import rule663 from './discovery_enumerating_domain_trusts_via_nltest.json'; +import rule664 from './credential_access_lsass_handle_via_malseclogon.json'; +import rule665 from './discovery_suspicious_self_subject_review.json'; +import rule666 from './initial_access_evasion_suspicious_htm_file_creation.json'; +import rule667 from './persistence_exposed_service_created_with_type_nodeport.json'; +import rule668 from './privilege_escalation_pod_created_with_hostipc.json'; +import rule669 from './privilege_escalation_pod_created_with_hostnetwork.json'; +import rule670 from './privilege_escalation_pod_created_with_hostpid.json'; +import rule671 from './privilege_escalation_privileged_pod_created.json'; +import rule672 from './execution_tc_bpf_filter.json'; +import rule673 from './persistence_insmod_kernel_module_load.json'; +import rule674 from './privilege_escalation_pod_created_with_sensitive_hostpath_volume.json'; +import rule675 from './persistence_dynamic_linker_backup.json'; +import rule676 from './defense_evasion_hidden_shared_object.json'; +import rule677 from './defense_evasion_chattr_immutable_file.json'; +import rule678 from './persistence_chkconfig_service_add.json'; +import rule679 from './persistence_etc_file_creation.json'; +import rule680 from './impact_process_kill_threshold.json'; +import rule681 from './discovery_posh_invoke_sharefinder.json'; +import rule682 from './privilege_escalation_posh_token_impersonation.json'; +import rule683 from './collection_google_drive_ownership_transferred_via_google_workspace.json'; +import rule684 from './persistence_google_workspace_user_group_access_modified_to_allow_external_access.json'; +import rule685 from './defense_evasion_application_removed_from_blocklist_in_google_workspace.json'; +import rule686 from './defense_evasion_google_workspace_restrictions_for_google_marketplace_changed_to_allow_any_app.json'; +import rule687 from './persistence_google_workspace_2sv_policy_disabled.json'; +import rule688 from './credential_access_generic_localdumps.json'; +import rule689 from './defense_evasion_persistence_temp_scheduled_task.json'; +import rule690 from './lateral_movement_remote_task_creation_winlog.json'; +import rule691 from './persistence_scheduled_task_creation_winlog.json'; +import rule692 from './persistence_scheduled_task_updated.json'; +import rule693 from './credential_access_saved_creds_vault_winlog.json'; +import rule694 from './privilege_escalation_create_process_as_different_user.json'; +import rule695 from './privilege_escalation_unshare_namesapce_manipulation.json'; +import rule696 from './privilege_escalation_shadow_file_read.json'; +import rule697 from './defense_evasion_google_workspace_bitlocker_setting_disabled.json'; +import rule698 from './persistence_google_workspace_user_organizational_unit_changed.json'; +import rule699 from './collection_google_workspace_custom_gmail_route_created_or_modified.json'; +import rule700 from './discovery_denied_service_account_request.json'; +import rule701 from './initial_access_anonymous_request_authorized.json'; +import rule702 from './privilege_escalation_suspicious_assignment_of_controller_service_account.json'; +import rule703 from './credential_access_bruteforce_passowrd_guessing.json'; +import rule704 from './credential_access_potential_linux_ssh_bruteforce.json'; +import rule705 from './credential_access_potential_linux_ssh_bruteforce_root.json'; +import rule706 from './privilege_escalation_container_created_with_excessive_linux_capabilities.json'; +import rule707 from './impact_kms_cmk_disabled_or_scheduled_for_deletion.json'; +import rule708 from './guided_onborading_sample_rule.json'; +import rule709 from './credential_access_wireless_creds_dumping.json'; +import rule710 from './defense_evasion_persistence_account_tokenfilterpolicy.json'; +import rule711 from './discovery_files_dir_systeminfo_via_cmd.json'; +import rule712 from './execution_reverse_shell_via_named_pipe.json'; + export const rawRules = [ rule1, rule2, @@ -1425,4 +1431,9 @@ export const rawRules = [ rule705, rule706, rule707, + rule708, + rule709, + rule710, + rule711, + rule712, ]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 3ed1a0fb62f359..72d867ae33fac7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -13,11 +13,22 @@ "license": "Elastic License v2", "name": "Kubernetes Anonymous Request Authorized", "note": "", - "query": "kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\" \n and (kubernetes.audit.user.username:(\"system:anonymous\" or \"system:unauthenticated\") or not kubernetes.audit.user.username:*)\n and not kubernetes.audit.objectRef.resource:(\"healthz\" or \"livez\" or \"readyz\")\n", + "query": "event.dataset : \"kubernetes.audit_logs\" \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\" \n and (kubernetes.audit.user.username:(\"system:anonymous\" or \"system:unauthenticated\") or not kubernetes.audit.user.username:*)\n and not kubernetes.audit.objectRef.resource:(\"healthz\" or \"livez\" or \"readyz\")\n", "references": [ "https://media.defense.gov/2022/Aug/29/2003066362/-1/-1/0/CTR_KUBERNETES_HARDENING_GUIDANCE_1.2_20220829.PDF" ], + "related_integrations": [ + { + "package": "kubernetes", + "version": "^1.4.1" + } + ], "required_fields": [ + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, { "ecs": false, "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", @@ -72,5 +83,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 77a7e0080caba4..45787397342d65 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -13,6 +13,11 @@ "name": "Unauthorized Access to an Okta Application", "note": "", "query": "event.dataset:okta.system and event.action:app.generic.unauth_app_access_attempt\n", + "references": [ + "https://developer.okta.com/docs/reference/api/system-log/", + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" + ], "related_integrations": [ { "package": "okta", @@ -89,5 +94,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index ce6ef31fa4a10f..a96ded43b09cbc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -17,7 +17,8 @@ "query": "event.dataset:okta.system and event.action:user.account.report_suspicious_activity_by_enduser\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -113,5 +114,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 9bc62b2b0ec533..f93b33390eae3f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,6 +14,9 @@ "name": "Suspicious MS Office Child Process", "note": "## Triage and analysis\n\n### Investigating Suspicious MS Office Child Process\n\nMicrosoft Office (MS Office) is a suite of applications designed to help with productivity and completing common tasks on a computer.\nYou can create and edit documents containing text and images, work with data in spreadsheets and databases, and create\npresentations and posters. As it is some of the most-used software across companies, MS Office is frequently targeted\nfor initial access. It also has a wide variety of capabilities that attackers can take advantage of.\n\nThis rule looks for suspicious processes spawned by MS Office programs. This is generally the result of the execution of\nmalicious documents.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Retrieve MS Office documents received and opened by the user that could cause this behavior. Common locations include,\nbut are not limited to, the Downloads and Document folders and the folder configured at the email client.\n- Determine if the collected files are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Benign true positives (B-TPs) can be added as exceptions if necessary.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Run a full scan using the antimalware tool in place. This scan can reveal additional artifacts left in the system,\npersistence mechanisms, and malware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n - If the malicious file was delivered via phishing:\n - Block the email sender from sending future emails.\n - Block the malicious web pages.\n - Remove emails from the sender from mailboxes.\n - Consider improvements to the security awareness program.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.name : (\"eqnedt32.exe\", \"excel.exe\", \"fltldr.exe\", \"msaccess.exe\", \"mspub.exe\", \"powerpnt.exe\", \"winword.exe\", \"outlook.exe\") and\n process.name : (\"Microsoft.Workflow.Compiler.exe\", \"arp.exe\", \"atbroker.exe\", \"bginfo.exe\", \"bitsadmin.exe\", \"cdb.exe\", \"certutil.exe\",\n \"cmd.exe\", \"cmstp.exe\", \"control.exe\", \"cscript.exe\", \"csi.exe\", \"dnx.exe\", \"dsget.exe\", \"dsquery.exe\", \"forfiles.exe\",\n \"fsi.exe\", \"ftp.exe\", \"gpresult.exe\", \"hostname.exe\", \"ieexec.exe\", \"iexpress.exe\", \"installutil.exe\", \"ipconfig.exe\",\n \"mshta.exe\", \"msxsl.exe\", \"nbtstat.exe\", \"net.exe\", \"net1.exe\", \"netsh.exe\", \"netstat.exe\", \"nltest.exe\", \"odbcconf.exe\",\n \"ping.exe\", \"powershell.exe\", \"pwsh.exe\", \"qprocess.exe\", \"quser.exe\", \"qwinsta.exe\", \"rcsi.exe\", \"reg.exe\", \"regasm.exe\",\n \"regsvcs.exe\", \"regsvr32.exe\", \"sc.exe\", \"schtasks.exe\", \"systeminfo.exe\", \"tasklist.exe\", \"tracert.exe\", \"whoami.exe\",\n \"wmic.exe\", \"wscript.exe\", \"xwizard.exe\", \"explorer.exe\", \"rundll32.exe\", \"hh.exe\", \"msdt.exe\")\n", + "references": [ + "https://www.elastic.co/blog/vulnerability-summary-follina" + ], "required_fields": [ { "ecs": true, @@ -69,5 +72,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a91bdd8faea3ed..1997c1fd721aec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -20,7 +20,8 @@ "references": [ "https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/", "https://msrc-blog.microsoft.com/2020/07/14/july-2020-security-update-cve-2020-1350-vulnerability-in-windows-domain-name-system-dns-server/", - "https://github.com/maxpl0it/CVE-2020-1350-DoS" + "https://github.com/maxpl0it/CVE-2020-1350-DoS", + "https://www.elastic.co/security-labs/detection-rules-for-sigred-vulnerability" ], "required_fields": [ { @@ -70,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index dcda7721281e5f..49f12a0b8f31a6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -16,7 +16,8 @@ "query": "file where process.name : \"dns.exe\" and event.type in (\"creation\", \"deletion\", \"change\") and\n not file.name : \"dns.log\"\n", "references": [ "https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/", - "https://msrc-blog.microsoft.com/2020/07/14/july-2020-security-update-cve-2020-1350-vulnerability-in-windows-domain-name-system-dns-server/" + "https://msrc-blog.microsoft.com/2020/07/14/july-2020-security-update-cve-2020-1350-vulnerability-in-windows-domain-name-system-dns-server/", + "https://www.elastic.co/security-labs/detection-rules-for-sigred-vulnerability" ], "required_fields": [ { @@ -65,5 +66,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 1d985fb800a682..de15f958c8ae02 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://research.checkpoint.com/2020/resolving-your-way-into-domain-admin-exploiting-a-17-year-old-bug-in-windows-dns-servers/", "https://msrc-blog.microsoft.com/2020/07/14/july-2020-security-update-cve-2020-1350-vulnerability-in-windows-domain-name-system-dns-server/", - "https://github.com/maxpl0it/CVE-2020-1350-DoS" + "https://github.com/maxpl0it/CVE-2020-1350-DoS", + "https://www.elastic.co/security-labs/detection-rules-for-sigred-vulnerability" ], "required_fields": [ { @@ -81,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 301ce56a51f5d8..07cccf7dc80449 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -57,7 +57,8 @@ "Host", "Windows", "Threat Detection", - "Lateral Movement" + "Lateral Movement", + "has_guide" ], "threat": [ { @@ -84,5 +85,5 @@ } ], "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 431397b6401073..7e279f63d6f892 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -50,6 +50,7 @@ "Host", "Windows", "Threat Detection", + "Initial Access", "Lateral Movement" ], "threat": [ @@ -74,9 +75,31 @@ ] } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0001", + "name": "Initial Access", + "reference": "https://attack.mitre.org/tactics/TA0001/" + }, + "technique": [ + { + "id": "T1078", + "name": "Valid Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/", + "subtechnique": [ + { + "id": "T1078.003", + "name": "Local Accounts", + "reference": "https://attack.mitre.org/techniques/T1078/003/" + } + ] + } + ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 45144cc4868959..64e5a58a731fc7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -92,7 +92,8 @@ "Host", "Windows", "Threat Detection", - "Lateral Movement" + "Lateral Movement", + "has_guide" ], "threat": [ { @@ -112,5 +113,5 @@ } ], "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 18fe87682f81e1..2add443b9b72cc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,7 +14,8 @@ "query": "event.dataset:okta.system and event.action:security.threat.detected\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -48,5 +49,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index be6ab836b1c0c2..d8269fa3511789 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/administrators-admin-comparison.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -69,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 4d22e033ac124c..a5c53099ae305c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/administrators-admin-comparison.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 66147953d146da..c33a7fef7fa009 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -17,7 +17,8 @@ "query": "event.dataset:okta.system and event.action:system.api_token.create\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c0338b37b53e33..9ab3fdf00bae33 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -17,7 +17,8 @@ "query": "event.dataset:okta.system and event.action:user.mfa.factor.deactivate\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/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 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 index 0d230518055f92..5e71faab057443 100644 --- a/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 +++ 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 @@ -17,7 +17,8 @@ "query": "event.dataset:okta.system and event.action:user.mfa.factor.reset_all\n", "references": [ "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -68,5 +69,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index d455613c5e1331..6dd54ff59c25cb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -54,14 +54,7 @@ { "id": "T1547", "name": "Boot or Logon Autostart Execution", - "reference": "https://attack.mitre.org/techniques/T1547/", - "subtechnique": [ - { - "id": "T1547.011", - "name": "Plist Modification", - "reference": "https://attack.mitre.org/techniques/T1547/011/" - } - ] + "reference": "https://attack.mitre.org/techniques/T1547/" } ] }, @@ -86,9 +79,24 @@ ] } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1647", + "name": "Plist File Modification", + "reference": "https://attack.mitre.org/techniques/T1647/" + } + ] } ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 23a7fc745bb683..f0e22bbbd04d24 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -13,13 +13,29 @@ "license": "Elastic License v2", "name": "Kubernetes Exposed Service Created With Type NodePort", "note": "", - "query": "kubernetes.audit.objectRef.resource:\"services\" and kubernetes.audit.verb:(\"create\" or \"update\" or \"patch\") and kubernetes.audit.requestObject.spec.type:\"NodePort\"\n", + "query": "event.dataset : \"kubernetes.audit_logs\" \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\" \n and kubernetes.audit.objectRef.resource:\"services\" \n and kubernetes.audit.verb:(\"create\" or \"update\" or \"patch\") \n and kubernetes.audit.requestObject.spec.type:\"NodePort\"\n", "references": [ "https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types", "https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport", "https://www.tigera.io/blog/new-vulnerability-exposes-kubernetes-to-man-in-the-middle-attacks-heres-how-to-mitigate/" ], + "related_integrations": [ + { + "package": "kubernetes", + "version": "^1.4.1" + } + ], "required_fields": [ + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": false, + "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.objectRef.resource", @@ -66,5 +82,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index ad5a36a1cb5e4e..ff3561d8af15a0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -16,6 +16,10 @@ "license": "Elastic License v2", "name": "Local Scheduled Task Creation", "query": "sequence with maxspan=1m\n [process where event.type != \"end\" and\n ((process.name : (\"cmd.exe\", \"wscript.exe\", \"rundll32.exe\", \"regsvr32.exe\", \"wmic.exe\", \"mshta.exe\",\n \"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\", \"WmiPrvSe.exe\", \"wsmprovhost.exe\", \"winrshost.exe\") or\n process.pe.original_file_name : (\"cmd.exe\", \"wscript.exe\", \"rundll32.exe\", \"regsvr32.exe\", \"wmic.exe\", \"mshta.exe\",\n \"powershell.exe\", \"pwsh.dll\", \"powershell_ise.exe\", \"WmiPrvSe.exe\", \"wsmprovhost.exe\",\n \"winrshost.exe\")) or\n process.code_signature.trusted == false)] by process.entity_id\n [process where event.type == \"start\" and\n (process.name : \"schtasks.exe\" or process.pe.original_file_name == \"schtasks.exe\") and\n process.args : (\"/create\", \"-create\") and process.args : (\"/RU\", \"/SC\", \"/TN\", \"/TR\", \"/F\", \"/XML\") and\n /* exclude SYSTEM Integrity Level - look for task creations by non-SYSTEM user */\n not (?process.Ext.token.integrity_level_name : \"System\" or ?winlog.event_data.IntegrityLevel : \"System\")\n ] by process.parent.entity_id\n", + "references": [ + "https://www.elastic.co/security-labs/hunting-for-persistence-using-elastic-security-part-1", + "https://www.elastic.co/security-labs/hunting-for-persistence-using-elastic-security-part-2" + ], "required_fields": [ { "ecs": true, @@ -98,5 +102,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 937f34263d3822..9b8b1c1b9da5f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -60,19 +60,27 @@ { "id": "T1547", "name": "Boot or Logon Autostart Execution", - "reference": "https://attack.mitre.org/techniques/T1547/", - "subtechnique": [ - { - "id": "T1547.011", - "name": "Plist Modification", - "reference": "https://attack.mitre.org/techniques/T1547/011/" - } - ] + "reference": "https://attack.mitre.org/techniques/T1547/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0005", + "name": "Defense Evasion", + "reference": "https://attack.mitre.org/tactics/TA0005/" + }, + "technique": [ + { + "id": "T1647", + "name": "Plist File Modification", + "reference": "https://attack.mitre.org/techniques/T1647/" } ] } ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/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 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 index c3de2846917005..c3fea13d482275 100644 --- a/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 +++ 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 @@ -18,7 +18,8 @@ "references": [ "https://help.okta.com/en/prod/Content/Topics/Security/App_Based_Signon.htm", "https://developer.okta.com/docs/reference/api/system-log/", - "https://developer.okta.com/docs/reference/api/event-types/" + "https://developer.okta.com/docs/reference/api/event-types/", + "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy" ], "related_integrations": [ { @@ -70,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 0c2e8424347505..c760ee9025211c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,11 +14,12 @@ "language": "eql", "license": "Elastic License v2", "name": "Account Password Reset Remotely", - "query": "sequence by host.id with maxspan=5m\n [authentication where event.action == \"logged-in\" and\n /* event 4624 need to be logged */\n winlog.logon.type : \"Network\" and event.outcome == \"success\" and source.ip != null and\n source.ip != \"127.0.0.1\" and source.ip != \"::1\"] by winlog.event_data.TargetLogonId\n /* event 4724 need to be logged */\n [iam where event.action == \"reset-password\" and\n (\n /*\n This rule is very noisy if not scoped to privileged accounts, duplicate the\n rule and add your own naming convention and accounts of interest here.\n */\n winlog.event_data.TargetUserName: (\"*Admin*\", \"*super*\", \"*SVC*\", \"*DC0*\", \"*service*\", \"*DMZ*\", \"*ADM*\") or\n winlog.event_data.TargetSid : \"S-1-5-21-*-500\"\n )\n ] by winlog.event_data.SubjectLogonId\n", + "query": "sequence by host.id with maxspan=5m\n [authentication where event.action == \"logged-in\" and\n /* event 4624 need to be logged */\n winlog.logon.type : \"Network\" and event.outcome == \"success\" and source.ip != null and\n source.ip != \"127.0.0.1\" and source.ip != \"::1\"] by winlog.event_data.TargetLogonId\n /* event 4724 need to be logged */\n [iam where event.action == \"reset-password\" and\n (\n /*\n This rule is very noisy if not scoped to privileged accounts, duplicate the\n rule and add your own naming convention and accounts of interest here.\n */\n winlog.event_data.TargetUserName: (\"*Admin*\", \"*super*\", \"*SVC*\", \"*DC0*\", \"*service*\", \"*DMZ*\", \"*ADM*\") or\n winlog.event_data.TargetSid : (\"S-1-5-21-*-500\", \"S-1-12-1-*-500\")\n )\n ] by winlog.event_data.SubjectLogonId\n", "references": [ "https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4724", "https://stealthbits.com/blog/manipulating-user-passwords-with-mimikatz/", - "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Credential%20Access/remote_pwd_reset_rpc_mimikatz_postzerologon_target_DC.evtx" + "https://github.com/sbousseaden/EVTX-ATTACK-SAMPLES/blob/master/Credential%20Access/remote_pwd_reset_rpc_mimikatz_postzerologon_target_DC.evtx", + "https://www.elastic.co/security-labs/detect-credential-access" ], "required_fields": [ { @@ -95,5 +96,5 @@ } ], "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 04da402d1caf9e..0c57577bf60675 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -52,7 +52,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -82,5 +83,5 @@ "timeline_title": "Comprehensive Registry Timeline", "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c7eb70ce22057d..444e4ccbcc1718 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,7 +14,7 @@ "language": "eql", "license": "Elastic License v2", "name": "A scheduled task was created", - "query": "iam where event.action == \"scheduled-task-created\" and\n\n /* excluding tasks created by the computer account */\n not user.name : \"*$\" and\n\n /* TaskContent is not parsed, exclude by full taskname noisy ones */\n not winlog.event_data.TaskName :\n (\"\\\\OneDrive Standalone Update Task-S-1-5-21*\",\n \"\\\\Hewlett-Packard\\\\HP Web Products Detection\",\n \"\\\\Hewlett-Packard\\\\HPDeviceCheck\")\n", + "query": "iam where event.action == \"scheduled-task-created\" and\n\n /* excluding tasks created by the computer account */\n not user.name : \"*$\" and\n\n /* TaskContent is not parsed, exclude by full taskname noisy ones */\n not winlog.event_data.TaskName :\n (\"\\\\OneDrive Standalone Update Task-S-1-5-21*\",\n \"\\\\OneDrive Standalone Update Task-S-1-12-1-*\",\n \"\\\\Hewlett-Packard\\\\HP Web Products Detection\",\n \"\\\\Hewlett-Packard\\\\HPDeviceCheck\")\n", "references": [ "https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4698" ], @@ -71,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 00d2a1c249cc80..40a9708e65c77c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,7 +14,7 @@ "language": "eql", "license": "Elastic License v2", "name": "A scheduled task was updated", - "query": "iam where event.action == \"scheduled-task-updated\" and\n\n /* excluding tasks created by the computer account */\n not user.name : \"*$\" and\n not winlog.event_data.TaskName :\n (\"\\\\User_Feed_Synchronization-*\",\n \"\\\\OneDrive Reporting Task-S-1-5-21*\",\n \"\\\\Hewlett-Packard\\\\HP Web Products Detection\",\n \"\\\\Hewlett-Packard\\\\HPDeviceCheck\")\n", + "query": "iam where event.action == \"scheduled-task-updated\" and\n\n /* excluding tasks created by the computer account */\n not user.name : \"*$\" and\n not winlog.event_data.TaskName :\n (\"\\\\User_Feed_Synchronization-*\",\n \"\\\\OneDrive Reporting Task-S-1-5-21*\",\n \"\\\\OneDrive Reporting Task-S-1-12-1-*\",\n \"\\\\Hewlett-Packard\\\\HP Web Products Detection\",\n \"\\\\Hewlett-Packard\\\\HPDeviceCheck\")\n", "references": [ "https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4698" ], @@ -71,5 +71,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a9375cd4f50ff6..5a19565ffa277a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -17,7 +17,8 @@ "note": "## Triage and analysis\n\n### Investigating Potential Shell via Web Server\n\nAdversaries may backdoor web servers with web shells to establish persistent access to systems. A web shell is a web\nscript that is placed on an openly accessible web server to allow an adversary to use the web server as a gateway into a\nnetwork. A web shell may provide a set of functions to execute or a command line interface on the system that hosts the\nweb server.\n\nThis rule detects a web server process spawning script and command line interface programs, potentially indicating\nattackers executing commands using the web shell.\n\n#### Possible investigation steps\n\n- Investigate abnormal behaviors observed by the subject process such as network connections, file modifications, and\nany other spawned child processes.\n- Examine the command line to determine which commands or scripts were executed.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- If scripts or executables were dropped, retrieve the files and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - Check if the domain is newly registered or unexpected.\n - Check the reputation of the domain or IP address.\n - File access, modification, and creation activities.\n - Cron jobs, services and other persistence mechanisms.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Any activity that triggered the alert and is not inherently\nmalicious must be monitored by the security team.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).\n", "query": "event.category:process and event.type:(start or process_started) and\nprocess.name:(bash or dash or ash or zsh or \"python*\" or \"perl*\" or \"php*\") and\nprocess.parent.name:(\"apache\" or \"nginx\" or \"www\" or \"apache2\" or \"httpd\" or \"www-data\")\n", "references": [ - "https://pentestlab.blog/tag/web-shell/" + "https://pentestlab.blog/tag/web-shell/", + "https://www.elastic.co/security-labs/elastic-response-to-the-the-spring4shell-vulnerability-cve-2022-22965" ], "required_fields": [ { @@ -49,7 +50,8 @@ "Host", "Linux", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -77,5 +79,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index b477cd01483523..bdce467458c4f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,6 +14,9 @@ "name": "Startup Persistence by a Suspicious Process", "note": "## Triage and analysis\n\n### Investigating Startup Persistence by a Suspicious Process\n\nThe Windows Startup folder is a special folder in Windows. Programs added to this folder are executed during account\nlogon, without user interaction, providing an excellent way for attackers to maintain persistence.\n\nThis rule monitors for commonly abused processes writing to the Startup folder locations.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Validate the activity is not related to planned patches, updates, network administrator activity, or legitimate\nsoftware installations.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Administrators may add programs to this mechanism via command-line shells. Before the further investigation,\nverify that this activity is not benign.\n\n### Related rules\n\n- Suspicious Startup Shell Folder Modification - c8b150f0-0164-475b-a75e-74b47800a9ff\n- Persistent Scripts in the Startup Directory - f7c4dc5a-a58d-491d-9f14-9b66507121c0\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "file where event.type != \"deletion\" and\n user.domain != \"NT AUTHORITY\" and\n file.path : (\"C:\\\\Users\\\\*\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\Startup\\\\*\",\n \"C:\\\\ProgramData\\\\Microsoft\\\\Windows\\\\Start Menu\\\\Programs\\\\StartUp\\\\*\") and\n process.name : (\"cmd.exe\",\n \"powershell.exe\",\n \"wmic.exe\",\n \"mshta.exe\",\n \"pwsh.exe\",\n \"cscript.exe\",\n \"wscript.exe\",\n \"regsvr32.exe\",\n \"RegAsm.exe\",\n \"rundll32.exe\",\n \"EQNEDT32.EXE\",\n \"WINWORD.EXE\",\n \"EXCEL.EXE\",\n \"POWERPNT.EXE\",\n \"MSPUB.EXE\",\n \"MSACCESS.EXE\",\n \"iexplore.exe\",\n \"InstallUtil.exe\")\n", + "references": [ + "https://www.elastic.co/security-labs/hunting-for-persistence-using-elastic-security-part-1" + ], "required_fields": [ { "ecs": true, @@ -74,5 +77,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 35a9201a43fa5c..7b0921a0d9bc26 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -11,7 +11,7 @@ "license": "Elastic License v2", "name": "Component Object Model Hijacking", "note": "## Triage and analysis\n\n### Investigating Component Object Model Hijacking\n\nAdversaries can insert malicious code that can be executed in place of legitimate software through hijacking the COM references and relationships as a means of persistence.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- Retrieve the file referenced in the registry and determine if it is malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- Some Microsoft executables will reference the LocalServer32 registry key value for the location of external COM objects.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", - "query": "registry where\n (registry.path : \"HK*}\\\\InprocServer32\\\\\" and registry.data.strings: (\"scrobj.dll\", \"C:\\\\*\\\\scrobj.dll\") and\n not registry.path : \"*\\\\{06290BD*-48AA-11D2-8432-006008C3FBFC}\\\\*\")\n or\n /* in general COM Registry changes on Users Hive is less noisy and worth alerting */\n (registry.path : (\"HKEY_USERS\\\\*Classes\\\\*\\\\InprocServer32\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\LocalServer32\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\DelegateExecute\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\*\\\\TreatAs\\\\\",\n \"HKEY_USERS\\\\*Classes\\\\CLSID\\\\*\\\\ScriptletURL\\\\\") and\n not (process.executable : \"?:\\\\Program Files*\\\\Veeam\\\\Backup and Replication\\\\Console\\\\veeam.backup.shell.exe\" and\n registry.path : \"HKEY_USERS\\\\S-1-5-21-*_Classes\\\\CLSID\\\\*\\\\LocalServer32\\\\\") and\n /* not necessary but good for filtering privileged installations */\n user.domain != \"NT AUTHORITY\"\n ) and\n /* removes false-positives generated by OneDrive and Teams */\n not process.name : (\"OneDrive.exe\",\"OneDriveSetup.exe\",\"FileSyncConfig.exe\",\"Teams.exe\") and\n /* Teams DLL loaded by regsvr */\n not (process.name: \"regsvr32.exe\" and\n registry.data.strings : \"*Microsoft.Teams.*.dll\")\n", + "query": "registry where\n /* not necessary but good for filtering privileged installations */\n user.domain != \"NT AUTHORITY\" and\n\n(\n (registry.path : \"HK*\\\\InprocServer32\\\\\" and registry.data.strings: (\"scrobj.dll\", \"C:\\\\*\\\\scrobj.dll\") and\n not registry.path : \"*\\\\{06290BD*-48AA-11D2-8432-006008C3FBFC}\\\\*\") or\n\n /* in general COM Registry changes on Users Hive is less noisy and worth alerting */\n (registry.path : (\"HKEY_USERS\\\\*\\\\InprocServer32\\\\*\",\n \"HKEY_USERS\\\\*\\\\LocalServer32\\\\*\",\n \"HKEY_USERS\\\\*\\\\DelegateExecute\\\\*\",\n \"HKEY_USERS\\\\*\\\\TreatAs\\\\*\",\n \"HKEY_USERS\\\\*\\\\ScriptletURL\\\\*\") and\n not (process.executable : \"?:\\\\Program Files*\\\\Veeam\\\\Backup and Replication\\\\Console\\\\veeam.backup.shell.exe\" and\n registry.path : \"HKEY_USERS\\\\S-1-*_Classes\\\\CLSID\\\\*\\\\LocalServer32\\\\\")) or\n\n (registry.path : \"HKLM\\\\*\\\\InProcServer32\\\\*\" and registry.data.strings : (\"*\\\\Users\\\\*\", \"*\\\\ProgramData\\\\*\"))\n\n) and\n\n /* removes false-positives generated by OneDrive and Teams */\n not process.name : (\"OneDrive.exe\",\"OneDriveSetup.exe\",\"FileSyncConfig.exe\",\"Teams.exe\") and\n\n /* Teams DLL loaded by regsvr */\n not (process.name: \"regsvr32.exe\" and registry.data.strings : \"*Microsoft.Teams.*.dll\")\n", "references": [ "https://bohops.com/2018/08/18/abusing-the-com-registry-structure-part-2-loading-techniques-for-evasion-and-persistence/" ], @@ -80,5 +80,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index ec9c831670716b..75421c73dafd43 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -11,7 +11,7 @@ ], "language": "eql", "license": "Elastic License v2", - "name": "User Added to Privileged Group in Active Directory", + "name": "User Added to Privileged Group", "note": "## Triage and analysis\n\n### Investigating User Added to Privileged Group in Active Directory\n\nPrivileged accounts and groups in Active Directory are those to which powerful rights, privileges, and permissions are\ngranted that allow them to perform nearly any action in Active Directory and on domain-joined systems.\n\nAttackers can add users to privileged groups to maintain a level of access if their other privileged accounts are\nuncovered by the security team. This allows them to keep operating after the security team discovers abused accounts.\n\nThis rule monitors events related to a user being added to a privileged group.\n\n#### Possible investigation steps\n\n- Identify the user account that performed the action and whether it should manage members of this group.\n- Contact the account owner and confirm whether they are aware of this activity.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- This attack abuses a legitimate Active Directory mechanism, so it is important to determine whether the activity is\nlegitimate, if the administrator is authorized to perform this operation, and if there is a need to grant the account\nthis level of privilege.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- If the admin is not aware of the operation, activate your Active Directory incident response plan.\n- If the user does not need the administrator privileges, remove the account from the privileged group.\n- Review the privileges of the administrator account that performed the action.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "iam where event.action == \"added-member-to-group\" and\n group.name : (\"Admin*\",\n \"Local Administrators\",\n \"Domain Admins\",\n \"Enterprise Admins\",\n \"Backup Admins\",\n \"Schema Admins\",\n \"DnsAdmins\",\n \"Exchange Organization Administrators\")\n", "references": [ @@ -60,5 +60,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 85997cb5399cdd..3a8bbc3fa9a344 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -12,7 +12,7 @@ "language": "eql", "license": "Elastic License v2", "name": "User Account Creation", - "note": "## Triage and analysis\n\n### Investigating User Account Creation\n\nAttackers may create new accounts (both local and domain) to maintain access to victim systems.\n\nThis rule identifies the usage of `net.exe` to create new accounts.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Identify if the account was added to privileged groups or assigned special privileges after creation.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- Account creation is a common administrative task, so there is a high chance of the activity being legitimate. Before\ninvestigating further, verify that this activity is not benign.\n\n### Related rules\n\n- Creation of a Hidden Local User Account - 2edc8076-291e-41e9-81e4-e3fcbc97ae5e\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Delete the created account.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", + "note": "## Triage and analysis\n\n### Investigating User Account Creation\n\nAttackers may create new accounts (both local and domain) to maintain access to victim systems.\n\nThis rule identifies the usage of `net.exe` to create new accounts.\n\n#### Possible investigation steps\n\n- Investigate the process execution chain (parent process tree) for unknown processes. Examine their executable files\nfor prevalence, whether they are located in expected locations, and if they are signed with valid digital signatures.\n- Identify the user account that performed the action and whether it should perform this kind of action.\n- Identify if the account was added to privileged groups or assigned special privileges after creation.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n\n### False positive analysis\n\n- Account creation is a common administrative task, so there is a high chance of the activity being legitimate. Before\ninvestigating further, verify that this activity is not benign.\n\n### Related rules\n\n- Creation of a Hidden Local User Account - 2edc8076-291e-41e9-81e4-e3fcbc97ae5e\n- Windows User Account Creation - 38e17753-f581-4644-84da-0d60a8318694\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- Delete the created account.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.name : (\"net.exe\", \"net1.exe\") and\n not process.parent.name : \"net.exe\" and\n (process.args : \"user\" and process.args : (\"/ad\", \"/add\"))\n", "required_fields": [ { @@ -74,5 +74,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index ace6bd37597830..38256544b51d15 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -13,13 +13,18 @@ "license": "Elastic License v2", "name": "Potential Application Shimming via Sdbinst", "note": "", - "query": "process where event.type == \"start\" and process.name : \"sdbinst.exe\"\n", + "query": "process where event.type == \"start\" and process.name : \"sdbinst.exe\" and \n not (process.args : \"-m\" and process.args : \"-bg\") and \n not process.args : \"-mm\"\n", "required_fields": [ { "ecs": true, "name": "event.type", "type": "keyword" }, + { + "ecs": true, + "name": "process.args", + "type": "keyword" + }, { "ecs": true, "name": "process.name", @@ -85,5 +90,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 48361327460572..56a519fc041af8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -54,7 +54,8 @@ "Windows", "Threat Detection", "Persistence", - "CVE-2020-1313" + "CVE-2020-1313", + "has_guide" ], "threat": [ { @@ -82,5 +83,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 3e5111b45a93d7..2ae10b454a9cf2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -14,6 +14,9 @@ "name": "Persistence via WMI Event Subscription", "note": "", "query": "process where event.type == \"start\" and\n (process.name : \"wmic.exe\" or process.pe.original_file_name == \"wmic.exe\") and\n process.args : \"create\" and\n process.args : (\"ActiveScriptEventConsumer\", \"CommandLineEventConsumer\")\n", + "references": [ + "https://www.elastic.co/security-labs/hunting-for-persistence-using-elastic-security-part-1" + ], "required_fields": [ { "ecs": true, @@ -73,5 +76,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index c69d5859d996b3..111b56638f9c8d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -12,7 +12,8 @@ "name": "Persistence via WMI Standard Registry Provider", "query": "registry where\n registry.data.strings != null and process.name : \"WmiPrvSe.exe\" and\n registry.path : (\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\*\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\*\",\n \"HKLM\\\\Software\\\\WOW6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\\\\*\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer\\\\Run\\\\*\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer\\\\Run\\\\*\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnce\\\\*\",\n \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnceEx\\\\*\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnce\\\\*\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\RunOnceEx\\\\*\",\n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\*\\\\ServiceDLL\",\n \"HKLM\\\\SYSTEM\\\\*ControlSet*\\\\Services\\\\*\\\\ImagePath\",\n \"HKEY_USERS\\\\*\\\\Software\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Winlogon\\\\Shell\\\\*\",\n \"HKEY_USERS\\\\*\\\\Environment\\\\UserInitMprLogonScript\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Windows\\\\Load\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Winlogon\\\\Shell\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System\\\\Shell\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\System\\\\Scripts\\\\Logoff\\\\Script\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\System\\\\Scripts\\\\Logon\\\\Script\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\System\\\\Scripts\\\\Shutdown\\\\Script\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows\\\\System\\\\Scripts\\\\Startup\\\\Script\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Microsoft\\\\Ctf\\\\LangBarAddin\\\\*\\\\FilePath\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Microsoft\\\\Internet Explorer\\\\Extensions\\\\*\\\\Exec\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Microsoft\\\\Internet Explorer\\\\Extensions\\\\*\\\\Script\",\n \"HKEY_USERS\\\\*\\\\SOFTWARE\\\\Microsoft\\\\Command Processor\\\\Autorun\"\n )\n", "references": [ - "https://docs.microsoft.com/en-us/previous-versions/windows/desktop/regprov/stdregprov" + "https://docs.microsoft.com/en-us/previous-versions/windows/desktop/regprov/stdregprov", + "https://www.elastic.co/security-labs/hunting-for-persistence-using-elastic-security-part-1" ], "required_fields": [ { @@ -94,5 +95,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 69130e81a668af..31e5786737b4d5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -18,7 +18,9 @@ "note": "## Triage and analysis\n\n### Investigating Web Shell Detection: Script Process Child of Common Web Processes\n\nAdversaries may backdoor web servers with web shells to establish persistent access to systems. A web shell is a web\nscript that is placed on an openly accessible web server to allow an adversary to use the web server as a gateway into a\nnetwork. A web shell may provide a set of functions to execute or a command-line interface on the system that hosts the\nweb server.\n\nThis rule detects a web server process spawning script and command-line interface programs, potentially indicating\nattackers executing commands using the web shell.\n\n#### Possible investigation steps\n\n- Investigate abnormal behaviors observed by the subject process such as network connections, registry or file\nmodifications, and any other spawned child processes.\n- Examine the command line to determine which commands or scripts were executed.\n- Investigate other alerts associated with the user/host during the past 48 hours.\n- Assess whether this behavior is prevalent in the environment by looking for similar occurrences across hosts.\n- If scripts or executables were dropped, retrieve the files and determine if they are malicious:\n - Use a private sandboxed malware analysis system to perform analysis.\n - Observe and collect information about the following activities:\n - Attempts to contact external domains and addresses.\n - File and registry access, modification, and creation activities.\n - Service creation and launch activities.\n - Scheduled task creation.\n - Use the PowerShell Get-FileHash cmdlet to get the files' SHA-256 hash values.\n - Search for the existence and reputation of the hashes in resources like VirusTotal, Hybrid-Analysis, CISCO Talos, Any.run, etc.\n\n### False positive analysis\n\n- This activity is unlikely to happen legitimately. Any activity that triggered the alert and is not inherently\nmalicious must be monitored by the security team.\n\n### Response and remediation\n\n- Initiate the incident response process based on the outcome of the triage.\n- Isolate the involved host to prevent further post-compromise behavior.\n- If the triage identified malware, search the environment for additional compromised hosts.\n - Implement temporary network rules, procedures, and segmentation to contain the malware.\n - Stop suspicious processes.\n - Immediately block the identified indicators of compromise (IoCs).\n - Inspect the affected systems for additional malware backdoors like reverse shells, reverse proxies, or droppers that\n attackers could use to reinfect the system.\n- Remove and block malicious artifacts identified during triage.\n- Investigate credential exposure on systems compromised or used by the attacker to ensure all compromised accounts are\nidentified. Reset passwords for these accounts and other potentially compromised credentials, such as email, business\nsystems, and web services.\n- Run a full antimalware scan. This may reveal additional artifacts left in the system, persistence mechanisms, and\nmalware components.\n- Determine the initial vector abused by the attacker and take action to prevent reinfection through the same vector.\n- Using the incident response data, update logging and audit policies to improve the mean time to detect (MTTD) and the\nmean time to respond (MTTR).", "query": "process where event.type == \"start\" and\n process.parent.name : (\"w3wp.exe\", \"httpd.exe\", \"nginx.exe\", \"php.exe\", \"php-cgi.exe\", \"tomcat.exe\") and\n process.name : (\"cmd.exe\", \"cscript.exe\", \"powershell.exe\", \"pwsh.exe\", \"powershell_ise.exe\", \"wmic.exe\", \"wscript.exe\")\n", "references": [ - "https://www.microsoft.com/security/blog/2020/02/04/ghost-in-the-shell-investigating-web-shell-attacks/" + "https://www.microsoft.com/security/blog/2020/02/04/ghost-in-the-shell-investigating-web-shell-attacks/", + "https://www.elastic.co/security-labs/elastic-response-to-the-the-spring4shell-vulnerability-cve-2022-22965", + "https://www.elastic.co/security-labs/hunting-for-persistence-using-elastic-security-part-1" ], "required_fields": [ { @@ -46,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Persistence" + "Persistence", + "has_guide" ], "threat": [ { @@ -89,5 +92,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_container_created_with_excessive_linux_capabilities.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_container_created_with_excessive_linux_capabilities.json new file mode 100644 index 00000000000000..7d8a95d7ad1412 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_container_created_with_excessive_linux_capabilities.json @@ -0,0 +1,107 @@ +{ + "author": [ + "Elastic" + ], + "description": "This rule detects a container deployed with one or more dangerously permissive Linux capabilities. An attacker with the ability to deploy a container with added capabilities could use this for further execution, lateral movement, or privilege escalation within a cluster. The capabilities detected in this rule have been used in container escapes to the host machine.", + "false_positives": [ + "Some container images require the addition of privileged capabilities. This rule leaves space for the exception of trusted container images. To add an exception, add the trusted container image name to the query field, kubernetes.audit.requestObject.spec.containers.image." + ], + "index": [ + "logs-kubernetes.*" + ], + "language": "kuery", + "license": "Elastic License v2", + "name": "Kubernetes Container Created with Excessive Linux Capabilities", + "note": "## Triage and analysis\n\n### Investigating Kubernetes Container Created with Excessive Linux Capabilities\n\nLinux capabilities were designed to divide root privileges into smaller units. Each capability grants a thread just enough power to perform specific privileged tasks. In Kubernetes, containers are given a set of default capabilities that can be dropped or added to at the time of creation. Added capabilities entitle containers in a pod with additional privileges that can be used to change\ncore processes, change network settings of a cluster, or directly access the underlying host. The following have been used in container escape techniques:\n\nBPF - Allow creating BPF maps, loading BPF Type Format (BTF) data, retrieve JITed code of BPF programs, and more. \nDAC_READ_SEARCH - Bypass file read permission checks and directory read and execute permission checks. \nNET_ADMIN - Perform various network-related operations. \nSYS_ADMIN - Perform a range of system administration operations. \nSYS_BOOT - Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution. \nSYS_MODULE - Load and unload kernel modules. \nSYS_PTRACE - Trace arbitrary processes using ptrace(2).\nSYS_RAWIO - Perform I/O port operations (iopl(2) and ioperm(2)). \nSYSLOG - Perform privileged syslog(2) operations.\n\n### False positive analysis\n\n- While these capabilities are not included by default in containers, some legitimate images may need to add them. This rule leaves space for the exception of trusted container images. To add an exception, add the trusted container image name to the query field, kubernetes.audit.requestObject.spec.containers.image.", + "query": "event.dataset: kubernetes.audit_logs \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\"\n and kubernetes.audit.verb: create \n and kubernetes.audit.objectRef.resource: pods \n and kubernetes.audit.requestObject.spec.containers.securityContext.capabilities.add: (\"BPF\" or \"DAC_READ_SEARCH\" or \"NET_ADMIN\" or \"SYS_ADMIN\" or \"SYS_BOOT\" or \"SYS_MODULE\" or \"SYS_PTRACE\" or \"SYS_RAWIO\" or \"SYSLOG\") \n and not kubernetes.audit.requestObject.spec.containers.image : (\"docker.elastic.co/beats/elastic-agent:8.4.0\" or \"rancher/klipper-lb:v0.3.5\" or \"\")\n", + "references": [ + "https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container", + "https://0xn3va.gitbook.io/cheat-sheets/container/escaping/excessive-capabilities", + "https://man7.org/linux/man-pages/man7/capabilities.7.html", + "https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities" + ], + "related_integrations": [ + { + "package": "kubernetes", + "version": "^1.4.1" + } + ], + "required_fields": [ + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": false, + "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", + "type": "unknown" + }, + { + "ecs": false, + "name": "kubernetes.audit.objectRef.resource", + "type": "unknown" + }, + { + "ecs": false, + "name": "kubernetes.audit.requestObject.spec.containers.image", + "type": "unknown" + }, + { + "ecs": false, + "name": "kubernetes.audit.requestObject.spec.containers.securityContext.capabilities.add", + "type": "unknown" + }, + { + "ecs": false, + "name": "kubernetes.audit.verb", + "type": "unknown" + } + ], + "risk_score": 47, + "rule_id": "7164081a-3930-11ed-a261-0242ac120002", + "setup": "The Kubernetes Fleet integration with Audit Logs enabled or similarly structured data is required to be compatible with this rule.", + "severity": "medium", + "tags": [ + "Elastic", + "Kubernetes", + "Continuous Monitoring", + "Execution", + "Privilege Escalation" + ], + "threat": [ + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0004", + "name": "Privilege Escalation", + "reference": "https://attack.mitre.org/tactics/TA0004/" + }, + "technique": [ + { + "id": "T1611", + "name": "Escape to Host", + "reference": "https://attack.mitre.org/techniques/T1611/" + } + ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1610", + "name": "Deploy Container", + "reference": "https://attack.mitre.org/techniques/T1610/" + } + ] + } + ], + "timestamp_override": "event.ingested", + "type": "query", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 764bd90d8d5ac2..625cc38ba927f1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -12,7 +12,7 @@ "license": "Elastic License v2", "name": "Process Creation via Secondary Logon", "note": "", - "query": "sequence by host.id with maxspan=1m\n\n[authentication where event.action:\"logged-in\" and\n event.outcome == \"success\" and user.id:\"S-1-5-21-*\" and\n\n /* seclogon service */\n process.name == \"svchost.exe\" and \n winlog.event_data.LogonProcessName : \"seclogo*\" and source.ip == \"::1\" ] by winlog.event_data.TargetLogonId\n\n[process where event.type == \"start\"] by winlog.event_data.TargetLogonId\n", + "query": "sequence by host.id with maxspan=1m\n\n[authentication where event.action:\"logged-in\" and\n event.outcome == \"success\" and user.id : (\"S-1-5-21-*\", \"S-1-12-1-*\") and\n\n /* seclogon service */\n process.name == \"svchost.exe\" and \n winlog.event_data.LogonProcessName : \"seclogo*\" and source.ip == \"::1\" ] by winlog.event_data.TargetLogonId\n\n[process where event.type == \"start\"] by winlog.event_data.TargetLogonId\n", "references": [ "https://attack.mitre.org/techniques/T1134/002/" ], @@ -104,5 +104,5 @@ } ], "type": "eql", - "version": 1 + "version": 2 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 04291353bfe135..833268fac1e0a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -45,7 +45,8 @@ "Host", "Windows", "Threat Detection", - "Privilege Escalation" + "Privilege Escalation", + "has_guide" ], "threat": [ { @@ -95,5 +96,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 deleted file mode 100644 index 41d7c2e58474bc..00000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "author": [ - "Elastic", - "Austin Songer" - ], - "description": "Identifies the creation or patching of potentially malicious role bindings. Users can use role bindings and cluster role bindings to assign roles to Kubernetes subjects (users, groups, or service accounts).", - "from": "now-20m", - "index": [ - "filebeat-*", - "logs-gcp*" - ], - "language": "kuery", - "license": "Elastic License v2", - "name": "GCP Kubernetes Rolebindings Created or Patched", - "note": "", - "query": "event.dataset:(googlecloud.audit or gcp.audit) and event.action:(io.k8s.authorization.rbac.v*.clusterrolebindings.create or\nio.k8s.authorization.rbac.v*.rolebindings.create or io.k8s.authorization.rbac.v*.clusterrolebindings.patch or\nio.k8s.authorization.rbac.v*.rolebindings.patch) and event.outcome:success and\nnot gcp.audit.authentication_info.principal_email:\"system:addon-manager\"\n", - "references": [ - "https://cloud.google.com/kubernetes-engine/docs/how-to/audit-logging", - "https://unofficial-kubernetes.readthedocs.io/en/latest/admin/authorization/rbac/", - "https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control" - ], - "related_integrations": [ - { - "integration": "audit", - "package": "gcp", - "version": "^2.2.1" - } - ], - "required_fields": [ - { - "ecs": true, - "name": "event.action", - "type": "keyword" - }, - { - "ecs": true, - "name": "event.dataset", - "type": "keyword" - }, - { - "ecs": true, - "name": "event.outcome", - "type": "keyword" - }, - { - "ecs": false, - "name": "gcp.audit.authentication_info.principal_email", - "type": "keyword" - } - ], - "risk_score": 47, - "rule_id": "2f0bae2d-bf20-4465-be86-1311addebaa3", - "setup": "The GCP Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule.", - "severity": "medium", - "tags": [ - "Elastic", - "Cloud", - "GCP", - "Continuous Monitoring", - "SecOps", - "Configuration Audit" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0004", - "name": "Privilege Escalation", - "reference": "https://attack.mitre.org/tactics/TA0004/" - }, - "technique": [] - } - ], - "timestamp_override": "event.ingested", - "type": "query", - "version": 101 -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index b116edce296b6a..9e36c14a5e90a0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -69,7 +69,8 @@ "Windows", "Threat Detection", "Persistence", - "Privilege Escalation" + "Privilege Escalation", + "has_guide" ], "threat": [ { @@ -119,5 +120,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index f8db0da46de0e6..61f058989707df 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -2,9 +2,9 @@ "author": [ "Elastic" ], - "description": "This rule detects an attempt to create or modify a pod using the host IPC namespace. This gives access to data used by any pod that also use the host\ufffds IPC namespace. If any process on the host or any processes in a pod uses the host\ufffds inter-process communication mechanisms (shared memory, semaphore arrays, message queues, etc.), an attacker can read/write to those same mechanisms. They may look for files in /dev/shm or use ipcs to check for any IPC facilities being used.", + "description": "This rule detects an attempt to create or modify a pod using the host IPC namespace. This gives access to data used by any pod that also use the hosts IPC namespace. If any process on the host or any processes in a pod uses the hosts inter-process communication mechanisms (shared memory, semaphore arrays, message queues, etc.), an attacker can read/write to those same mechanisms. They may look for files in /dev/shm or use ipcs to check for any IPC facilities being used.", "false_positives": [ - "An administrator or developer may want to use a pod that runs as root and shares the host\ufffds IPC, Network, and PID namespaces for debugging purposes. If something is going wrong in the cluster and there is no easy way to SSH onto the host nodes directly, a privileged pod of this nature can be useful for viewing things like iptable rules and network namespaces from the host's perspective." + "An administrator or developer may want to use a pod that runs as root and shares the host's IPC, Network, and PID namespaces for debugging purposes. If something is going wrong in the cluster and there is no easy way to SSH onto the host nodes directly, a privileged pod of this nature can be useful for viewing things like iptable rules and network namespaces from the host's perspective. Add exceptions for trusted container images using the query field \"kubernetes.audit.requestObject.spec.container.image\"" ], "index": [ "logs-kubernetes.*" @@ -13,18 +13,39 @@ "license": "Elastic License v2", "name": "Kubernetes Pod Created With HostIPC", "note": "", - "query": "kubernetes.audit.objectRef.resource:\"pods\" and kubernetes.audit.verb:(\"create\" or \"update\" or \"patch\") and kubernetes.audit.requestObject.spec.hostIPC:true\n", + "query": "event.dataset : \"kubernetes.audit_logs\" \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\" \n and kubernetes.audit.objectRef.resource:\"pods\" \n and kubernetes.audit.verb:(\"create\" or \"update\" or \"patch\") \n and kubernetes.audit.requestObject.spec.hostIPC:true\n and not kubernetes.audit.requestObject.spec.containers.image: (\"docker.elastic.co/beats/elastic-agent:8.4.0\")\n", "references": [ "https://research.nccgroup.com/2021/11/10/detection-engineering-for-kubernetes-clusters/#part3-kubernetes-detections", "https://kubernetes.io/docs/concepts/security/pod-security-policy/#host-namespaces", "https://bishopfox.com/blog/kubernetes-pod-privilege-escalation" ], + "related_integrations": [ + { + "package": "kubernetes", + "version": "^1.4.1" + } + ], "required_fields": [ + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": false, + "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.objectRef.resource", "type": "unknown" }, + { + "ecs": false, + "name": "kubernetes.audit.requestObject.spec.containers.image", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.requestObject.spec.hostIPC", @@ -62,9 +83,24 @@ "reference": "https://attack.mitre.org/techniques/T1611/" } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1610", + "name": "Deploy Container", + "reference": "https://attack.mitre.org/techniques/T1610/" + } + ] } ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 689cca88265af4..42797635419a54 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -4,7 +4,7 @@ ], "description": "This rules detects an attempt to create or modify a pod attached to the host network. HostNetwork allows a pod to use the node network namespace. Doing so gives the pod access to any service running on localhost of the host. An attacker could use this access to snoop on network activity of other pods on the same node or bypass restrictive network policies applied to its given namespace.", "false_positives": [ - "An administrator or developer may want to use a pod that runs as root and shares the host\ufffds IPC, Network, and PID namespaces for debugging purposes. If something is going wrong in the cluster and there is no easy way to SSH onto the host nodes directly, a privileged pod of this nature can be useful for viewing things like iptable rules and network namespaces from the host's perspective." + "An administrator or developer may want to use a pod that runs as root and shares the hosts IPC, Network, and PID namespaces for debugging purposes. If something is going wrong in the cluster and there is no easy way to SSH onto the host nodes directly, a privileged pod of this nature can be useful for viewing things like iptable rules and network namespaces from the host's perspective. Add exceptions for trusted container images using the query field \"kubernetes.audit.requestObject.spec.container.image\"" ], "index": [ "logs-kubernetes.*" @@ -13,18 +13,39 @@ "license": "Elastic License v2", "name": "Kubernetes Pod Created With HostNetwork", "note": "", - "query": "kubernetes.audit.objectRef.resource:\"pods\" and kubernetes.audit.verb:(\"create\" or \"update\" or \"patch\") and kubernetes.audit.requestObject.spec.hostNetwork:true\n", + "query": "event.dataset : \"kubernetes.audit_logs\" \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\" \n and kubernetes.audit.objectRef.resource:\"pods\" \n and kubernetes.audit.verb:(\"create\" or \"update\" or \"patch\") \n and kubernetes.audit.requestObject.spec.hostNetwork:true\n and not kubernetes.audit.requestObject.spec.containers.image: (\"docker.elastic.co/beats/elastic-agent:8.4.0\")\n", "references": [ "https://research.nccgroup.com/2021/11/10/detection-engineering-for-kubernetes-clusters/#part3-kubernetes-detections", "https://kubernetes.io/docs/concepts/security/pod-security-policy/#host-namespaces", "https://bishopfox.com/blog/kubernetes-pod-privilege-escalation" ], + "related_integrations": [ + { + "package": "kubernetes", + "version": "^1.4.1" + } + ], "required_fields": [ + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": false, + "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.objectRef.resource", "type": "unknown" }, + { + "ecs": false, + "name": "kubernetes.audit.requestObject.spec.containers.image", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.requestObject.spec.hostNetwork", @@ -62,9 +83,24 @@ "reference": "https://attack.mitre.org/techniques/T1611/" } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1610", + "name": "Deploy Container", + "reference": "https://attack.mitre.org/techniques/T1610/" + } + ] } ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index a3da4f7e9ae897..d7a6baae8efa78 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -4,7 +4,7 @@ ], "description": "This rule detects an attempt to create or modify a pod attached to the host PID namespace. HostPID allows a pod to access all the processes running on the host and could allow an attacker to take malicious action. When paired with ptrace this can be used to escalate privileges outside of the container. When paired with a privileged container, the pod can see all of the processes on the host. An attacker can enter the init system (PID 1) on the host. From there, they could execute a shell and continue to escalate privileges to root.", "false_positives": [ - "An administrator or developer may want to use a pod that runs as root and shares the host\ufffds IPC, Network, and PID namespaces for debugging purposes. If something is going wrong in the cluster and there is no easy way to SSH onto the host nodes directly, a privileged pod of this nature can be useful for viewing things like iptable rules and network namespaces from the host's perspective." + "An administrator or developer may want to use a pod that runs as root and shares the hosts IPC, Network, and PID namespaces for debugging purposes. If something is going wrong in the cluster and there is no easy way to SSH onto the host nodes directly, a privileged pod of this nature can be useful for viewing things like iptable rules and network namespaces from the host's perspective. Add exceptions for trusted container images using the query field \"kubernetes.audit.requestObject.spec.container.image\"" ], "index": [ "logs-kubernetes.*" @@ -13,18 +13,39 @@ "license": "Elastic License v2", "name": "Kubernetes Pod Created With HostPID", "note": "", - "query": "kubernetes.audit.objectRef.resource:\"pods\" and kubernetes.audit.verb:(\"create\" or \"update\" or \"patch\") and kubernetes.audit.requestObject.spec.hostPID:true\n", + "query": "event.dataset : \"kubernetes.audit_logs\" \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\" \n and kubernetes.audit.objectRef.resource:\"pods\" \n and kubernetes.audit.verb:(\"create\" or \"update\" or \"patch\") \n and kubernetes.audit.requestObject.spec.hostPID:true\n and not kubernetes.audit.requestObject.spec.containers.image: (\"docker.elastic.co/beats/elastic-agent:8.4.0\")\n", "references": [ "https://research.nccgroup.com/2021/11/10/detection-engineering-for-kubernetes-clusters/#part3-kubernetes-detections", "https://kubernetes.io/docs/concepts/security/pod-security-policy/#host-namespaces", "https://bishopfox.com/blog/kubernetes-pod-privilege-escalation" ], + "related_integrations": [ + { + "package": "kubernetes", + "version": "^1.4.1" + } + ], "required_fields": [ + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": false, + "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.objectRef.resource", "type": "unknown" }, + { + "ecs": false, + "name": "kubernetes.audit.requestObject.spec.containers.image", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.requestObject.spec.hostPID", @@ -62,9 +83,24 @@ "reference": "https://attack.mitre.org/techniques/T1611/" } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1610", + "name": "Deploy Container", + "reference": "https://attack.mitre.org/techniques/T1610/" + } + ] } ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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_hostpath_volume.json similarity index 58% rename from x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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_hostpath_volume.json index dcd1ad8e178dd3..aa9352ec83cbd9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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_hostpath_volume.json @@ -4,7 +4,7 @@ ], "description": "This rule detects when a pod is created with a sensitive volume of type hostPath. A hostPath volume type mounts a sensitive file or folder from the node to the container. If the container gets compromised, the attacker can use this mount for gaining access to the node. There are many ways a container with unrestricted access to the host filesystem can escalate privileges, including reading data from other containers, and accessing tokens of more privileged pods.", "false_positives": [ - "An administrator may need to attach a hostPath volume for a legitimate reason. This alert should be investigated for legitimacy by determining if the kuberenetes.audit.requestObject.spec.volumes.hostPath.path triggered is one needed by its target container/pod. For example, when the fleet managed elastic agent is deployed as a daemonset it creates several hostPath volume mounts, some of which are sensitive host directories like /proc, /etc/kubernetes, and /var/log." + "An administrator may need to attach a hostPath volume for a legitimate reason. This alert should be investigated for legitimacy by determining if the kuberenetes.audit.requestObject.spec.volumes.hostPath.path triggered is one needed by its target container/pod. For example, when the fleet managed elastic agent is deployed as a daemonset it creates several hostPath volume mounts, some of which are sensitive host directories like /proc, /etc/kubernetes, and /var/log. Add exceptions for trusted container images using the query field \"kubernetes.audit.requestObject.spec.container.image\"" ], "index": [ "logs-kubernetes.*" @@ -13,17 +13,38 @@ "license": "Elastic License v2", "name": "Kubernetes Pod created with a Sensitive hostPath Volume", "note": "", - "query": "kubernetes.audit.objectRef.resource:\"pods\"\n and kubernetes.audit.verb:(\"create\" or \"update\" or \"patch\")\n and kubernetes.audit.requestObject.spec.volumes.hostPath.path:(\"/\" or \"/proc\" or \"/root\" or \"/var\" or \"/var/run/docker.sock\" or \"/var/run/crio/crio.sock\" or \"/var/run/cri-dockerd.sock\" or \"/var/lib/kubelet\" or \"/var/lib/kubelet/pki\" or \"/var/lib/docker/overlay2\" or \"/etc\" or \"/etc/kubernetes\" or \"/etc/kubernetes/manifests\" or \"/home/admin\")\n", + "query": "event.dataset : \"kubernetes.audit_logs\" \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\" \n and kubernetes.audit.objectRef.resource:\"pods\"\n and kubernetes.audit.verb:(\"create\" or \"update\" or \"patch\")\n and kubernetes.audit.requestObject.spec.volumes.hostPath.path:\n (\"/\" or \n \"/proc\" or \n \"/root\" or \n \"/var\" or \n \"/var/run\" or \n \"/var/run/docker.sock\" or \n \"/var/run/crio/crio.sock\" or \n \"/var/run/cri-dockerd.sock\" or \n \"/var/lib/kubelet\" or \n \"/var/lib/kubelet/pki\" or \n \"/var/lib/docker/overlay2\" or \n \"/etc\" or \n \"/etc/kubernetes\" or \n \"/etc/kubernetes/manifests\" or \n \"/etc/kubernetes/pki\" or\n \"/home/admin\")\n and not kubernetes.audit.requestObject.spec.containers.image: (\"docker.elastic.co/beats/elastic-agent:8.4.0\")\n", "references": [ "https://blog.appsecco.com/kubernetes-namespace-breakout-using-insecure-host-path-volume-part-1-b382f2a6e216", "https://kubernetes.io/docs/concepts/storage/volumes/#hostpath" ], + "related_integrations": [ + { + "package": "kubernetes", + "version": "^1.4.1" + } + ], "required_fields": [ + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": false, + "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.objectRef.resource", "type": "unknown" }, + { + "ecs": false, + "name": "kubernetes.audit.requestObject.spec.containers.image", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.requestObject.spec.volumes.hostPath.path", @@ -61,9 +82,24 @@ "reference": "https://attack.mitre.org/techniques/T1611/" } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1610", + "name": "Deploy Container", + "reference": "https://attack.mitre.org/techniques/T1610/" + } + ] } ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 365caf6dd14266..3c323e23e29547 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -48,7 +48,8 @@ "Host", "Windows", "Threat Detection", - "Privilege Escalation" + "Privilege Escalation", + "has_guide" ], "threat": [ { @@ -69,5 +70,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 101 + "version": 102 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index 3cde880bfcdd50..e7ce744e72437f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -4,7 +4,7 @@ ], "description": "This rule detects when a user creates a pod/container running in privileged mode. A highly privileged container has access to the node's resources and breaks the isolation between containers. If compromised, an attacker can use the privileged container to gain access to the underlying host. Gaining access to the host may provide the adversary with the opportunity to achieve follow-on objectives, such as establishing persistence, moving laterally within the environment, or setting up a command and control channel on the host.", "false_positives": [ - "By default a container is not allowed to access any devices on the host, but a \"privileged\" container is given access to all devices on the host. This allows the container nearly all the same access as processes running on the host. An administrator may want to run a privileged container to use operating system administrative capabilities such as manipulating the network stack or accessing hardware devices from within the cluster." + "By default a container is not allowed to access any devices on the host, but a \"privileged\" container is given access to all devices on the host. This allows the container nearly all the same access as processes running on the host. An administrator may want to run a privileged container to use operating system administrative capabilities such as manipulating the network stack or accessing hardware devices from within the cluster. Add exceptions for trusted container images using the query field \"kubernetes.audit.requestObject.spec.container.image\"" ], "index": [ "logs-kubernetes.*" @@ -13,17 +13,38 @@ "license": "Elastic License v2", "name": "Kubernetes Privileged Pod Created", "note": "", - "query": "kubernetes.audit.objectRef.resource:pods and kubernetes.audit.verb:create and\n kubernetes.audit.requestObject.spec.containers.securityContext.privileged:true\n", + "query": "event.dataset : \"kubernetes.audit_logs\" \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\"\n and kubernetes.audit.objectRef.resource:pods \n and kubernetes.audit.verb:create \n and kubernetes.audit.requestObject.spec.containers.securityContext.privileged:true\n and not kubernetes.audit.requestObject.spec.containers.image: (\"docker.elastic.co/beats/elastic-agent:8.4.0\")\n", "references": [ "https://media.defense.gov/2021/Aug/03/2002820425/-1/-1/1/CTR_KUBERNETES%20HARDENING%20GUIDANCE.PDF", "https://kubernetes.io/docs/tasks/configure-pod-container/security-context/" ], + "related_integrations": [ + { + "package": "kubernetes", + "version": "^1.4.1" + } + ], "required_fields": [ + { + "ecs": true, + "name": "event.dataset", + "type": "keyword" + }, + { + "ecs": false, + "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.objectRef.resource", "type": "unknown" }, + { + "ecs": false, + "name": "kubernetes.audit.requestObject.spec.containers.image", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.requestObject.spec.containers.securityContext.privileged", @@ -61,9 +82,24 @@ "reference": "https://attack.mitre.org/techniques/T1611/" } ] + }, + { + "framework": "MITRE ATT&CK", + "tactic": { + "id": "TA0002", + "name": "Execution", + "reference": "https://attack.mitre.org/tactics/TA0002/" + }, + "technique": [ + { + "id": "T1610", + "name": "Deploy Container", + "reference": "https://attack.mitre.org/techniques/T1610/" + } + ] } ], "timestamp_override": "event.ingested", "type": "query", - "version": 100 + "version": 101 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index fa217cf1cfd87f..4b2eb8cc7f0a73 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -13,7 +13,7 @@ "license": "Elastic License v2", "name": "Kubernetes Suspicious Assignment of Controller Service Account", "note": "", - "query": "event.dataset : \"kubernetes.audit_logs\" and kubernetes.audit.verb : \"create\" \n and kubernetes.audit.objectRef.resource : \"pods\"\n and kubernetes.audit.objectRef.namespace : \"kube-system\"\n and kubernetes.audit.requestObject.spec.serviceAccountName:*controller\n", + "query": "event.dataset : \"kubernetes.audit_logs\" \n and kubernetes.audit.annotations.authorization_k8s_io/decision:\"allow\"\n and kubernetes.audit.verb : \"create\" \n and kubernetes.audit.objectRef.resource : \"pods\"\n and kubernetes.audit.objectRef.namespace : \"kube-system\"\n and kubernetes.audit.requestObject.spec.serviceAccountName:*controller\n", "references": [ "https://www.paloaltonetworks.com/apps/pan/public/downloadResource?pagePath=/content/pan/en_US/resources/whitepapers/kubernetes-privilege-escalation-excessive-permissions-in-popular-platforms" ], @@ -29,6 +29,11 @@ "name": "event.dataset", "type": "keyword" }, + { + "ecs": false, + "name": "kubernetes.audit.annotations.authorization_k8s_io/decision", + "type": "unknown" + }, { "ecs": false, "name": "kubernetes.audit.objectRef.namespace", @@ -87,5 +92,5 @@ ], "timestamp_override": "event.ingested", "type": "query", - "version": 2 + "version": 3 } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 index d19097ca6f91bc..3e75a489518ba3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/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 @@ -15,7 +15,8 @@ "note": "", "query": "file where event.type : \"change\" and process.name : \"dllhost.exe\" and\n /* Known modules names side loaded into process running with high or system integrity level for UAC Bypass, update here for new modules */\n file.name : (\"wow64log.dll\", \"comctl32.dll\", \"DismCore.dll\", \"OskSupport.dll\", \"duser.dll\", \"Accessibility.ni.dll\") and\n /* has no impact on rule logic just to avoid OS install related FPs */\n not file.path : (\"C:\\\\Windows\\\\SoftwareDistribution\\\\*\", \"C:\\\\Windows\\\\WinSxS\\\\*\")\n", "references": [ - "https://github.com/hfiref0x/UACME" + "https://github.com/hfiref0x/UACME", + "https://www.elastic.co/security-labs/exploring-windows-uac-bypasses-techniques-and-detection-strategies" ], "required_fields": [ { @@ -76,5 +77,5 @@ ], "timestamp_override": "event.ingested", "type": "eql", - "version": 100 + "version": 101 } 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 67462232a22ba9..edb7c2c6998823 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 @@ -98,4 +98,5 @@ export const getOutputRuleAlertForRest = (): RuleResponse => ({ timestamp_override_fallback_disabled: undefined, namespace: undefined, data_view_id: undefined, + alert_suppression: undefined, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/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 index b82136e33acf28..a3f6b39cfa26c5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/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 @@ -62,6 +62,7 @@ describe('schedule_notification_actions', () => { relatedIntegrations: [], requiredFields: [], setup: '', + alertSuppression: undefined, }; it('Should schedule actions with unflatted and legacy context', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/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 index 517f9b0a16f585..701da673efcd6a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/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 @@ -63,6 +63,7 @@ describe('schedule_throttle_notification_actions', () => { relatedIntegrations: [], requiredFields: [], setup: '', + alertSuppression: undefined, }; }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts index 9a13833e08437e..6a658fe6e9dca8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts @@ -95,56 +95,7 @@ export const createRuleExceptionsRoute = (router: SecuritySolutionPluginRouter) }); } - let createdItems; - - const ruleDefaultLists = rule.params.exceptionsList.filter( - (list) => list.type === ExceptionListTypeEnum.RULE_DEFAULT - ); - - // This should hopefully never happen, but could if we forget to add such a check to one - // of our routes allowing the user to update the rule to have more than one default list added - checkDefaultRuleExceptionListReferences({ exceptionLists: rule.params.exceptionsList }); - - const [ruleDefaultList] = ruleDefaultLists; - - if (ruleDefaultList != null) { - // check that list does indeed exist - const exceptionListAssociatedToRule = await listsClient?.getExceptionList({ - id: ruleDefaultList.id, - listId: ruleDefaultList.list_id, - namespaceType: ruleDefaultList.namespace_type, - }); - - // if list does exist, just need to create the items - if (exceptionListAssociatedToRule != null) { - createdItems = await createExceptionListItems({ - items, - defaultList: exceptionListAssociatedToRule, - listsClient, - }); - } else { - // This means that there was missed cleanup when this rule exception list was - // deleted and it remained referenced on the rule. Let's remove it from the rule, - // and update the rule's exceptions lists to include newly created default list. - const defaultList = await createAndAssociateDefaultExceptionList({ - rule, - rulesClient, - listsClient, - removeOldAssociation: true, - }); - - createdItems = await createExceptionListItems({ items, defaultList, listsClient }); - } - } else { - const defaultList = await createAndAssociateDefaultExceptionList({ - rule, - rulesClient, - listsClient, - removeOldAssociation: false, - }); - - createdItems = await createExceptionListItems({ items, defaultList, listsClient }); - } + const createdItems = await createRuleExceptions({ items, rule, listsClient, rulesClient }); const [validated, errors] = validate(createdItems, t.array(exceptionListItemSchema)); if (errors != null) { @@ -163,6 +114,67 @@ export const createRuleExceptionsRoute = (router: SecuritySolutionPluginRouter) ); }; +export const createRuleExceptions = async ({ + items, + rule, + listsClient, + rulesClient, +}: { + items: CreateRuleExceptionListItemSchemaDecoded[]; + listsClient: ExceptionListClient | null; + rulesClient: RulesClient; + rule: SanitizedRule; +}) => { + const ruleDefaultLists = rule.params.exceptionsList.filter( + (list) => list.type === ExceptionListTypeEnum.RULE_DEFAULT + ); + + // This should hopefully never happen, but could if we forget to add such a check to one + // of our routes allowing the user to update the rule to have more than one default list added + checkDefaultRuleExceptionListReferences({ exceptionLists: rule.params.exceptionsList }); + + const [ruleDefaultList] = ruleDefaultLists; + + if (ruleDefaultList != null) { + // check that list does indeed exist + const exceptionListAssociatedToRule = await listsClient?.getExceptionList({ + id: ruleDefaultList.id, + listId: ruleDefaultList.list_id, + namespaceType: ruleDefaultList.namespace_type, + }); + + // if list does exist, just need to create the items + if (exceptionListAssociatedToRule != null) { + return createExceptionListItems({ + items, + defaultList: exceptionListAssociatedToRule, + listsClient, + }); + } else { + // This means that there was missed cleanup when this rule exception list was + // deleted and it remained referenced on the rule. Let's remove it from the rule, + // and update the rule's exceptions lists to include newly created default list. + const defaultList = await createAndAssociateDefaultExceptionList({ + rule, + rulesClient, + listsClient, + removeOldAssociation: true, + }); + + return createExceptionListItems({ items, defaultList, listsClient }); + } + } else { + const defaultList = await createAndAssociateDefaultExceptionList({ + rule, + rulesClient, + listsClient, + removeOldAssociation: false, + }); + + return createExceptionListItems({ items, defaultList, listsClient }); + } +}; + export const createExceptionListItems = async ({ items, defaultList, @@ -191,17 +203,15 @@ export const createExceptionListItems = async ({ ); }; -export const createAndAssociateDefaultExceptionList = async ({ +export const createExceptionList = async ({ rule, listsClient, - rulesClient, - removeOldAssociation, }: { rule: SanitizedRule; listsClient: ExceptionListClient | null; - rulesClient: RulesClient; - removeOldAssociation: boolean; -}): Promise => { +}): Promise => { + if (!listsClient) return null; + const exceptionList: CreateExceptionListSchema = { description: `Exception list containing exceptions for rule with id: ${rule.id}`, meta: undefined, @@ -233,7 +243,7 @@ export const createAndAssociateDefaultExceptionList = async ({ } = validated; // create the default rule list - const exceptionListAssociatedToRule = await listsClient?.createExceptionList({ + return listsClient.createExceptionList({ description, immutable: false, listId, @@ -244,8 +254,22 @@ export const createAndAssociateDefaultExceptionList = async ({ type, version, }); +}; + +export const createAndAssociateDefaultExceptionList = async ({ + rule, + listsClient, + rulesClient, + removeOldAssociation, +}: { + rule: SanitizedRule; + listsClient: ExceptionListClient | null; + rulesClient: RulesClient; + removeOldAssociation: boolean; +}): Promise => { + const exceptionListToAssociate = await createExceptionList({ rule, listsClient }); - if (exceptionListAssociatedToRule == null) { + if (exceptionListToAssociate == null) { throw Error(`An error occurred creating rule default exception list`); } @@ -265,14 +289,14 @@ export const createAndAssociateDefaultExceptionList = async ({ exceptions_list: [ ...ruleExceptionLists, { - id: exceptionListAssociatedToRule.id, - list_id: exceptionListAssociatedToRule.list_id, - type: exceptionListAssociatedToRule.type, - namespace_type: exceptionListAssociatedToRule.namespace_type, + id: exceptionListToAssociate.id, + list_id: exceptionListToAssociate.list_id, + type: exceptionListToAssociate.type, + namespace_type: exceptionListToAssociate.namespace_type, }, ], }, }); - return exceptionListAssociatedToRule; + return exceptionListToAssociate; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts index da19964e5b7ec0..3e884113f2c84e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts @@ -35,6 +35,7 @@ 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 { duplicateExceptions } from '../../../logic/actions/duplicate_exceptions'; import { findRules } from '../../../logic/search/find_rules'; import { readRules } from '../../../logic/crud/read_rules'; import { getExportByObjectIds } from '../../../logic/export/get_export_by_object_ids'; @@ -497,18 +498,46 @@ export const performBulkActionRoute = ( if (isDryRun) { return rule; } - const migratedRule = await migrateRuleActions({ rulesClient, savedObjectsClient, rule, }); + let shouldDuplicateExceptions = true; + if (body.duplicate !== undefined) { + shouldDuplicateExceptions = body.duplicate.include_exceptions; + } + + const duplicateRuleToCreate = await duplicateRule({ + rule: migratedRule, + }); const createdRule = await rulesClient.create({ - data: duplicateRule(migratedRule), + data: duplicateRuleToCreate, + }); + + // we try to create exceptions after rule created, and then update rule + const exceptions = shouldDuplicateExceptions + ? await duplicateExceptions({ + ruleId: rule.params.ruleId, + exceptionLists: rule.params.exceptionsList, + exceptionsClient, + }) + : []; + + const updatedRule = await rulesClient.update({ + id: createdRule.id, + data: { + ...duplicateRuleToCreate, + params: { + ...duplicateRuleToCreate.params, + exceptionsList: exceptions, + }, + }, }); - return createdRule; + // TODO: figureout why types can't return just updatedRule + return { ...createdRule, ...updatedRule }; }, abortSignal: abortController.signal, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_exceptions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_exceptions.ts new file mode 100644 index 00000000000000..496a91ba559634 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_exceptions.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 type { ExceptionListClient } from '@kbn/lists-plugin/server'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; + +import type { RuleParams } from '../../../rule_schema'; + +interface DuplicateExceptionsParams { + ruleId: RuleParams['ruleId']; + exceptionLists: RuleParams['exceptionsList']; + exceptionsClient: ExceptionListClient | undefined; +} + +export const duplicateExceptions = async ({ + ruleId, + exceptionLists, + exceptionsClient, +}: DuplicateExceptionsParams): Promise => { + if (exceptionLists == null) { + return []; + } + + // Sort the shared lists and the rule_default lists. + // Only a single rule_default list should exist per rule. + const ruleDefaultList = exceptionLists.find( + (list) => list.type === ExceptionListTypeEnum.RULE_DEFAULT + ); + const sharedLists = exceptionLists.filter( + (list) => list.type !== ExceptionListTypeEnum.RULE_DEFAULT + ); + + // For rule_default list (exceptions that live only on a single rule), we need + // to create a new rule_default list to assign to duplicated rule + if (ruleDefaultList != null && exceptionsClient != null) { + const ruleDefaultExceptionList = await exceptionsClient.duplicateExceptionListAndItems({ + listId: ruleDefaultList.list_id, + namespaceType: ruleDefaultList.namespace_type, + }); + + if (ruleDefaultExceptionList == null) { + throw new Error(`Unable to duplicate rule default exception items for rule_id: ${ruleId}`); + } + + return [ + ...sharedLists, + { + id: ruleDefaultExceptionList.id, + list_id: ruleDefaultExceptionList.list_id, + namespace_type: ruleDefaultExceptionList.namespace_type, + type: ruleDefaultExceptionList.type, + }, + ]; + } + + // If no rule_default list exists, we can just return + return [...sharedLists]; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts index 8637236f654d29..a36746623dbcde 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts @@ -61,6 +61,7 @@ describe('duplicateRule', () => { timestampOverride: undefined, timestampOverrideFallbackDisabled: undefined, dataViewId: undefined, + alertSuppression: undefined, }, schedule: { interval: '5m', @@ -90,9 +91,11 @@ describe('duplicateRule', () => { jest.clearAllMocks(); }); - it('returns an object with fields copied from a given rule', () => { + it('returns an object with fields copied from a given rule', async () => { const rule = createTestRule(); - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual({ name: expect.anything(), // covered in a separate test @@ -111,10 +114,12 @@ describe('duplicateRule', () => { }); }); - it('appends [Duplicate] to the name', () => { + it('appends [Duplicate] to the name', async () => { const rule = createTestRule(); rule.name = 'PowerShell Keylogging Script'; - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ @@ -123,9 +128,11 @@ describe('duplicateRule', () => { ); }); - it('generates a new ruleId', () => { + it('generates a new ruleId', async () => { const rule = createTestRule(); - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ @@ -136,10 +143,12 @@ describe('duplicateRule', () => { ); }); - it('makes sure the duplicated rule is disabled', () => { + it('makes sure the duplicated rule is disabled', async () => { const rule = createTestRule(); rule.enabled = true; - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ @@ -155,9 +164,11 @@ describe('duplicateRule', () => { return rule; }; - it('transforms it to a custom (mutable) rule', () => { + it('transforms it to a custom (mutable) rule', async () => { const rule = createPrebuiltRule(); - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ @@ -168,7 +179,7 @@ describe('duplicateRule', () => { ); }); - it('resets related integrations to an empty array', () => { + it('resets related integrations to an empty array', async () => { const rule = createPrebuiltRule(); rule.params.relatedIntegrations = [ { @@ -178,7 +189,9 @@ describe('duplicateRule', () => { }, ]; - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ @@ -189,7 +202,7 @@ describe('duplicateRule', () => { ); }); - it('resets required fields to an empty array', () => { + it('resets required fields to an empty array', async () => { const rule = createPrebuiltRule(); rule.params.requiredFields = [ { @@ -199,7 +212,9 @@ describe('duplicateRule', () => { }, ]; - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ @@ -210,10 +225,12 @@ describe('duplicateRule', () => { ); }); - it('resets setup guide to an empty string', () => { + it('resets setup guide to an empty string', async () => { const rule = createPrebuiltRule(); rule.params.setup = `## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured...`; - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ @@ -232,9 +249,11 @@ describe('duplicateRule', () => { return rule; }; - it('keeps it custom', () => { + it('keeps it custom', async () => { const rule = createCustomRule(); - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ @@ -245,7 +264,7 @@ describe('duplicateRule', () => { ); }); - it('copies related integrations as is', () => { + it('copies related integrations as is', async () => { const rule = createCustomRule(); rule.params.relatedIntegrations = [ { @@ -255,7 +274,9 @@ describe('duplicateRule', () => { }, ]; - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ @@ -266,7 +287,7 @@ describe('duplicateRule', () => { ); }); - it('copies required fields as is', () => { + it('copies required fields as is', async () => { const rule = createCustomRule(); rule.params.requiredFields = [ { @@ -276,7 +297,9 @@ describe('duplicateRule', () => { }, ]; - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ @@ -287,10 +310,12 @@ describe('duplicateRule', () => { ); }); - it('copies setup guide as is', () => { + it('copies setup guide as is', async () => { const rule = createCustomRule(); rule.params.setup = `## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured...`; - const result = duplicateRule(rule); + const result = await duplicateRule({ + rule, + }); expect(result).toEqual( expect.objectContaining({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts index 5f15a2ed81d1f5..f5ee4fc8ae35dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts @@ -9,7 +9,6 @@ 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 '../../../rule_schema'; @@ -20,7 +19,11 @@ const DUPLICATE_TITLE = i18n.translate( } ); -export const duplicateRule = (rule: SanitizedRule): InternalRuleCreate => { +interface DuplicateRuleParams { + rule: SanitizedRule; +} + +export const duplicateRule = async ({ rule }: DuplicateRuleParams): Promise => { // Generate a new static ruleId const ruleId = uuid.v4(); @@ -43,6 +46,7 @@ export const duplicateRule = (rule: SanitizedRule): InternalRuleCrea relatedIntegrations, requiredFields, setup, + exceptionsList: [], }, schedule: rule.schedule, enabled: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/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 index 0242f17509a992..02b83342cd8467 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/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 @@ -221,6 +221,7 @@ describe('get_export_by_object_ids', () => { timestamp_override_fallback_disabled: undefined, namespace: undefined, data_view_id: undefined, + alert_suppression: undefined, }, ], }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts index 82d6ee6b1c4b2f..e15c88ccf3aa16 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts @@ -82,6 +82,7 @@ import { transformToAlertThrottle, transformToNotifyWhen, } from './rule_actions'; +import { convertAlertSuppressionToCamel, convertAlertSuppressionToSnake } from '../utils/utils'; // 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 @@ -137,6 +138,7 @@ export const typeSpecificSnakeToCamel = ( filters: params.filters, savedId: params.saved_id, responseActions: params.response_actions?.map(transformRuleToAlertResponseAction), + alertSuppression: convertAlertSuppressionToCamel(params.alert_suppression), }; } case 'saved_query': { @@ -149,6 +151,7 @@ export const typeSpecificSnakeToCamel = ( savedId: params.saved_id, dataViewId: params.data_view_id, responseActions: params.response_actions?.map(transformRuleToAlertResponseAction), + alertSuppression: convertAlertSuppressionToCamel(params.alert_suppression), }; } case 'threshold': { @@ -243,6 +246,7 @@ const patchQueryParams = ( responseActions: params.response_actions?.map(transformRuleToAlertResponseAction) ?? existingRule.responseActions, + alertSuppression: convertAlertSuppressionToCamel(params.alert_suppression), }; }; @@ -261,6 +265,7 @@ const patchSavedQueryParams = ( responseActions: params.response_actions?.map(transformRuleToAlertResponseAction) ?? existingRule.responseActions, + alertSuppression: convertAlertSuppressionToCamel(params.alert_suppression), }; }; @@ -462,10 +467,10 @@ export const convertPatchAPIToInternalSchema = ( : existingRule.actions, throttle: nextParams.throttle ? transformToAlertThrottle(nextParams.throttle) - : existingRule.throttle, + : existingRule.throttle ?? null, notifyWhen: nextParams.throttle ? transformToNotifyWhen(nextParams.throttle) - : existingRule.notifyWhen, + : existingRule.notifyWhen ?? null, }; }; @@ -572,6 +577,7 @@ export const typeSpecificCamelToSnake = (params: TypeSpecificRuleParams): TypeSp filters: params.filters, saved_id: params.savedId, response_actions: params.responseActions?.map(transformAlertToRuleResponseAction), + alert_suppression: convertAlertSuppressionToSnake(params.alertSuppression), }; } case 'saved_query': { @@ -584,6 +590,7 @@ export const typeSpecificCamelToSnake = (params: TypeSpecificRuleParams): TypeSp saved_id: params.savedId, data_view_id: params.dataViewId, response_actions: params.responseActions?.map(transformAlertToRuleResponseAction), + alert_suppression: convertAlertSuppressionToSnake(params.alertSuppression), }; } case 'threshold': { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts index 3c8ca418013034..173e1a5f5b906a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts @@ -16,12 +16,15 @@ 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 { RuleResponse } from '../../../../../common/detection_engine/rule_schema'; +import type { + AlertSuppression, + RuleResponse, +} from '../../../../../common/detection_engine/rule_schema'; // eslint-disable-next-line no-restricted-imports import type { LegacyRulesActionsSavedObject } from '../../rule_actions_legacy'; import type { RuleExecutionSummariesByRuleId } from '../../rule_monitoring'; -import type { RuleAlertType, RuleParams } from '../../rule_schema'; +import type { AlertSuppressionCamel, RuleAlertType, RuleParams } from '../../rule_schema'; import { isAlertType } from '../../rule_schema'; import type { BulkError, OutputError } from '../../routes/utils'; import { createBulkErrorObject } from '../../routes/utils'; @@ -355,3 +358,21 @@ export const getInvalidConnectors = async ( return [Array.from(errors.values()), Array.from(rulesAcc.values())]; }; + +export const convertAlertSuppressionToCamel = ( + input: AlertSuppression | undefined +): AlertSuppressionCamel | undefined => + input + ? { + groupBy: input.group_by, + } + : undefined; + +export const convertAlertSuppressionToSnake = ( + input: AlertSuppressionCamel | undefined +): AlertSuppression | undefined => + input + ? { + group_by: input.groupBy, + } + : undefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts index 8d920ef4ba652a..f90ce4a33b5738 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts @@ -77,6 +77,7 @@ export const ruleOutput = (): RuleResponse => ({ namespace: undefined, data_view_id: undefined, saved_id: undefined, + alert_suppression: undefined, }); describe('validate', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts index 01bc55bf124670..5dbc62c86c4178 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts @@ -7,6 +7,7 @@ import moment from 'moment'; import uuid from 'uuid'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { QUERY_RULE_TYPE_ID, SAVED_QUERY_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import type { Logger, StartServicesAccessor } from '@kbn/core/server'; import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { @@ -51,7 +52,6 @@ import { createIndicatorMatchAlertType, createMlAlertType, createQueryAlertType, - createSavedQueryAlertType, createThresholdAlertType, createNewTermsAlertType, } from '../../../rule_types'; @@ -288,7 +288,12 @@ export const previewRulesRoute = async ( switch (previewRuleParams.type) { case 'query': const queryAlertType = previewRuleTypeWrapper( - createQueryAlertType({ ...ruleOptions, ...queryRuleAdditionalOptions }) + createQueryAlertType({ + ...ruleOptions, + ...queryRuleAdditionalOptions, + id: QUERY_RULE_TYPE_ID, + name: 'Custom Query Rule', + }) ); await runExecutors( queryAlertType.executor, @@ -308,7 +313,12 @@ export const previewRulesRoute = async ( break; case 'saved_query': const savedQueryAlertType = previewRuleTypeWrapper( - createSavedQueryAlertType({ ...ruleOptions, ...queryRuleAdditionalOptions }) + createQueryAlertType({ + ...ruleOptions, + ...queryRuleAdditionalOptions, + id: SAVED_QUERY_RULE_TYPE_ID, + name: 'Saved Query Rule', + }) ); await runExecutors( savedQueryAlertType.executor, @@ -403,9 +413,7 @@ export const previewRulesRoute = async ( ); break; case 'new_terms': - const newTermsAlertType = previewRuleTypeWrapper( - createNewTermsAlertType(ruleOptions, true) - ); + const newTermsAlertType = previewRuleTypeWrapper(createNewTermsAlertType(ruleOptions)); await runExecutors( newTermsAlertType.executor, newTermsAlertType.id, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts index 04cec44000f78f..3dcc8b0389cc97 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts @@ -125,6 +125,7 @@ export const getQueryRuleParams = (): QueryRuleParams => { }, ], savedId: undefined, + alertSuppression: undefined, responseActions: undefined, }; }; @@ -148,6 +149,7 @@ export const getSavedQueryRuleParams = (): SavedQueryRuleParams => { ], savedId: 'some-id', responseActions: undefined, + alertSuppression: undefined, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts index 0e64cc3788a12e..1be28852dadeb6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts @@ -39,6 +39,7 @@ import type { SanitizedRuleConfig } from '@kbn/alerting-plugin/common'; import { AlertsIndex, AlertsIndexNamespace, + AlertSuppressionGroupBy, BuildingBlockType, DataViewId, EventCategoryOverride, @@ -85,6 +86,13 @@ import { ResponseActionRuleParamsOrUndefined } from '../../../../../common/detec const nonEqlLanguages = t.keyof({ kuery: null, lucene: null }); +export type AlertSuppressionCamel = t.TypeOf; +const AlertSuppressionCamel = t.exact( + t.type({ + groupBy: AlertSuppressionGroupBy, + }) +); + export const baseRuleParams = t.exact( t.type({ author: RuleAuthorArray, @@ -168,6 +176,7 @@ const querySpecificRuleParams = t.exact( savedId: savedIdOrUndefined, dataViewId: t.union([DataViewId, t.undefined]), responseActions: ResponseActionRuleParamsOrUndefined, + alertSuppression: t.union([AlertSuppressionCamel, t.undefined]), }) ); export const queryRuleParams = t.intersection([baseRuleParams, querySpecificRuleParams]); @@ -185,6 +194,7 @@ const savedQuerySpecificRuleParams = t.type({ filters: t.union([RuleFilterArray, t.undefined]), savedId: saved_id, responseActions: ResponseActionRuleParamsOrUndefined, + alertSuppression: t.union([AlertSuppressionCamel, t.undefined]), }); export const savedQueryRuleParams = t.intersection([baseRuleParams, savedQuerySpecificRuleParams]); export type SavedQuerySpecificRuleParams = t.TypeOf; @@ -274,7 +284,7 @@ export const allRuleTypes = t.union([ t.literal(NEW_TERMS_RULE_TYPE_ID), ]); -export const internalRuleCreate = t.type({ +const internalRuleCreateRequired = t.type({ name: RuleName, tags: RuleTagArray, alertTypeId: allRuleTypes, @@ -285,12 +295,18 @@ export const internalRuleCreate = t.type({ enabled: IsRuleEnabled, actions: RuleActionArrayCamel, params: ruleParams, +}); +const internalRuleCreateOptional = t.partial({ throttle: t.union([RuleActionThrottle, t.null]), notifyWhen, }); +export const internalRuleCreate = t.intersection([ + internalRuleCreateOptional, + internalRuleCreateRequired, +]); export type InternalRuleCreate = t.TypeOf; -export const internalRuleUpdate = t.type({ +const internalRuleUpdateRequired = t.type({ name: RuleName, tags: RuleTagArray, schedule: t.type({ @@ -298,7 +314,13 @@ export const internalRuleUpdate = t.type({ }), actions: RuleActionArrayCamel, params: ruleParams, +}); +const internalRuleUpdateOptional = t.partial({ throttle: t.union([RuleActionThrottle, t.null]), notifyWhen, }); +export const internalRuleUpdate = t.intersection([ + internalRuleUpdateOptional, + internalRuleUpdateRequired, +]); export type InternalRuleUpdate = t.TypeOf; 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 8ee773bb91cf7a..8ec879c1e814c9 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 @@ -341,6 +341,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = secondaryTimestamp, ruleExecutionLogger, aggregatableTimestampField, + alertTimestampOverride, }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_suppressed_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_suppressed_alerts.ts new file mode 100644 index 00000000000000..e708e3d906efc5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_suppressed_alerts.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import objectHash from 'object-hash'; +import type * as estypes from '@elastic/elasticsearch/lib/api/types'; +import { + ALERT_UUID, + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_START, +} from '@kbn/rule-data-utils'; +import type { + BaseFieldsLatest, + SuppressionFieldsLatest, + WrappedFieldsLatest, +} from '../../../../../../common/detection_engine/schemas/alerts'; +import type { ConfigType } from '../../../../../config'; +import type { CompleteRule, RuleParams } from '../../../rule_schema'; +import type { SignalSource } from '../../../signals/types'; +import { buildBulkBody } from './build_bulk_body'; +import type { BuildReasonMessage } from '../../../signals/reason_formatters'; + +export interface SuppressionBuckets { + event: estypes.SearchHit; + count: number; + start: Date; + end: Date; + terms: Array<{ field: string; value: string | number | null }>; +} + +export const wrapSuppressedAlerts = ({ + suppressionBuckets, + spaceId, + completeRule, + mergeStrategy, + indicesToQuery, + buildReasonMessage, + alertTimestampOverride, +}: { + suppressionBuckets: SuppressionBuckets[]; + spaceId: string | null | undefined; + completeRule: CompleteRule; + mergeStrategy: ConfigType['alertMergeStrategy']; + indicesToQuery: string[]; + buildReasonMessage: BuildReasonMessage; + alertTimestampOverride: Date | undefined; +}): Array> => { + return suppressionBuckets.map((bucket) => { + const id = objectHash([ + bucket.event._index, + bucket.event._id, + String(bucket.event._version), + `${spaceId}:${completeRule.alertId}`, + bucket.terms, + bucket.start, + bucket.end, + ]); + const baseAlert: BaseFieldsLatest = buildBulkBody( + spaceId, + completeRule, + bucket.event, + mergeStrategy, + [], + true, + buildReasonMessage, + indicesToQuery, + alertTimestampOverride + ); + return { + _id: id, + _index: '', + _source: { + ...baseAlert, + [ALERT_SUPPRESSION_TERMS]: bucket.terms, + [ALERT_SUPPRESSION_START]: bucket.start, + [ALERT_SUPPRESSION_END]: bucket.end, + [ALERT_SUPPRESSION_DOCS_COUNT]: bucket.count - 1, + [ALERT_UUID]: id, + }, + }; + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts index 9c5743aa1451dd..8bcce2c7fd6c13 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts @@ -9,6 +9,5 @@ export { createEqlAlertType } from './eql/create_eql_alert_type'; export { createIndicatorMatchAlertType } from './indicator_match/create_indicator_match_alert_type'; export { createMlAlertType } from './ml/create_ml_alert_type'; export { createQueryAlertType } from './query/create_query_alert_type'; -export { createSavedQueryAlertType } from './saved_query/create_saved_query_alert_type'; export { createThresholdAlertType } from './threshold/create_threshold_alert_type'; export { createNewTermsAlertType } from './new_terms/create_new_terms_alert_type'; 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 bc2746ddf7888b..8ef5b3acf4b2c1 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 @@ -43,8 +43,7 @@ import { import { createEnrichEventsFunction } from '../../signals/enrichments'; export const createNewTermsAlertType = ( - createOptions: CreateRuleOptions, - isPreview?: boolean + createOptions: CreateRuleOptions ): SecurityAlertType => { const { logger } = createOptions; return { @@ -107,12 +106,12 @@ export const createNewTermsAlertType = ( aggregatableTimestampField, exceptionFilter, unprocessedExceptions, + alertTimestampOverride, }, services, params, spaceId, state, - startedAt, } = execOptions; // Validate the history window size compared to `from` at runtime as well as in the `validate` @@ -288,7 +287,6 @@ export const createNewTermsAlertType = ( }; }); - const alertTimestampOverride = isPreview ? startedAt : undefined; const wrappedAlerts = wrapNewTermsAlerts({ eventsAndTerms, spaceId, 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 c2ecb5a88df8ef..0478c4d49c1ba6 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 @@ -17,6 +17,7 @@ import { ruleExecutionLogMock } from '../../rule_monitoring/mocks'; import { sampleDocNoSortId } from '../../signals/__mocks__/es_results'; import { getQueryRuleParams } from '../../rule_schema/mocks'; import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; +import { QUERY_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; jest.mock('../../signals/utils', () => ({ ...jest.requireActual('../../signals/utils'), @@ -59,6 +60,8 @@ describe('Custom Query Alerts', () => { experimentalFeatures: allowedExperimentalValues, logger, version: '1.0.0', + id: QUERY_RULE_TYPE_ID, + name: 'Custom Query Rule', }) ); @@ -105,6 +108,8 @@ describe('Custom Query Alerts', () => { experimentalFeatures: allowedExperimentalValues, logger, version: '1.0.0', + id: QUERY_RULE_TYPE_ID, + name: 'Custom Query Rule', }) ); 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 4f246e5ada204d..89a43f896a4d91 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 @@ -6,23 +6,35 @@ */ 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 { BucketHistory } from '../../signals/alert_suppression/group_and_bulk_create'; 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'; +export interface QueryRuleState { + suppressionGroupHistory?: BucketHistory[]; + [key: string]: unknown; +} + export const createQueryAlertType = ( createOptions: CreateQueryRuleOptions -): SecurityAlertType => { - const { eventsTelemetry, experimentalFeatures, version, osqueryCreateAction, licensing } = - createOptions; +): SecurityAlertType => { + const { + eventsTelemetry, + experimentalFeatures, + version, + osqueryCreateAction, + licensing, + id, + name, + } = createOptions; return { - id: QUERY_RULE_TYPE_ID, - name: 'Custom Query Rule', + id, + name, validate: { params: { validate: (object: unknown) => { @@ -62,47 +74,18 @@ export const createQueryAlertType = ( isExportable: false, producer: SERVER_APP_ID, async executor(execOptions) { - const { - runOpts: { - inputIndex, - runtimeMappings, - completeRule, - tuple, - listClient, - ruleExecutionLogger, - searchAfterSize, - bulkCreate, - wrapHits, - primaryTimestamp, - secondaryTimestamp, - unprocessedExceptions, - exceptionFilter, - }, - services, - state, - } = execOptions; - const result = await queryExecutor({ - completeRule, - tuple, - listClient, + const { runOpts, services, spaceId, state } = execOptions; + return queryExecutor({ + runOpts, experimentalFeatures, - ruleExecutionLogger, eventsTelemetry, services, version, - searchAfterSize, - bulkCreate, - wrapHits, - inputIndex, - runtimeMappings, - primaryTimestamp, - secondaryTimestamp, - unprocessedExceptions, - exceptionFilter, + spaceId, + bucketHistory: state.suppressionGroupHistory, osqueryCreateAction, licensing, }); - return { ...result, state }; }, }; }; 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 deleted file mode 100644 index 6e761bb6a51a02..00000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts +++ /dev/null @@ -1,108 +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 { 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 '../../rule_schema'; -import { unifiedQueryRuleParams } from '../../rule_schema'; -import { queryExecutor } from '../../signals/executors/query'; -import type { CreateQueryRuleOptions, SecurityAlertType } from '../types'; -import { validateIndexPatterns } from '../utils'; - -export const createSavedQueryAlertType = ( - createOptions: CreateQueryRuleOptions -): SecurityAlertType => { - const { experimentalFeatures, version, osqueryCreateAction, licensing } = createOptions; - return { - id: SAVED_QUERY_RULE_TYPE_ID, - name: 'Saved Query Rule', - validate: { - params: { - validate: (object: unknown) => { - const [validated, errors] = validateNonExact(object, unifiedQueryRuleParams); - if (errors != null) { - throw new Error(errors); - } - if (validated == null) { - throw new Error('Validation of rule params failed'); - } - return validated; - }, - /** - * validate rule params when rule is bulk edited (update and created in future as well) - * returned params can be modified (useful in case of version increment) - * @param mutatedRuleParams - * @returns mutatedRuleParams - */ - validateMutatedParams: (mutatedRuleParams) => { - validateIndexPatterns(mutatedRuleParams.index); - - return mutatedRuleParams; - }, - }, - }, - actionGroups: [ - { - id: 'default', - name: 'Default', - }, - ], - defaultActionGroupId: 'default', - actionVariables: { - context: [{ name: 'server', description: 'the server' }], - }, - minimumLicenseRequired: 'basic', - isExportable: false, - producer: SERVER_APP_ID, - async executor(execOptions) { - const { - runOpts: { - inputIndex, - runtimeMappings, - completeRule, - tuple, - listClient, - ruleExecutionLogger, - searchAfterSize, - bulkCreate, - wrapHits, - primaryTimestamp, - secondaryTimestamp, - exceptionFilter, - unprocessedExceptions, - }, - services, - state, - } = execOptions; - - const result = await queryExecutor({ - inputIndex, - runtimeMappings, - completeRule: completeRule as CompleteRule, - tuple, - experimentalFeatures, - listClient, - ruleExecutionLogger, - eventsTelemetry: undefined, - services, - version, - searchAfterSize, - bulkCreate, - wrapHits, - primaryTimestamp, - secondaryTimestamp, - exceptionFilter, - unprocessedExceptions, - osqueryCreateAction, - licensing, - }); - return { ...result, state }; - }, - }; -}; 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 ffd9e587361e9b..4bbc32b371108a 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 @@ -11,6 +11,8 @@ import type { Logger } from '@kbn/logging'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { QUERY_RULE_TYPE_ID, SAVED_QUERY_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; + import type { RuleExecutorOptions, RuleType } from '@kbn/alerting-plugin/server'; import type { AlertInstanceContext, @@ -76,6 +78,7 @@ export interface RunOpts { aggregatableTimestampField: string; unprocessedExceptions: ExceptionListItemSchema[]; exceptionFilter: Filter | undefined; + alertTimestampOverride: Date | undefined; } export type SecurityAlertType< @@ -136,4 +139,7 @@ export interface CreateQueryRuleAdditionalOptions { export interface CreateQueryRuleOptions extends CreateRuleOptions, - CreateQueryRuleAdditionalOptions {} + CreateQueryRuleAdditionalOptions { + id: typeof QUERY_RULE_TYPE_ID | typeof SAVED_QUERY_RULE_TYPE_ID; + name: 'Custom Query Rule' | 'Saved Query Rule'; +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 29ba601a75705d..5bf47929cffa53 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -517,6 +517,7 @@ export const sampleSignalHit = (): SignalHit => ({ data_view_id: undefined, filters: undefined, saved_id: undefined, + alert_suppression: undefined, }, depth: 1, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/__snapshots__/build_group_by_field_aggregation.test.ts.snap b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/__snapshots__/build_group_by_field_aggregation.test.ts.snap new file mode 100644 index 00000000000000..c1b21b1de1db1c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/__snapshots__/build_group_by_field_aggregation.test.ts.snap @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`build_group_by_field_aggregation Build Group-by-field aggregation 1`] = ` +Object { + "eventGroups": Object { + "aggs": Object { + "max_timestamp": Object { + "max": Object { + "field": "kibana.combined_timestamp", + }, + }, + "min_timestamp": Object { + "min": Object { + "field": "kibana.combined_timestamp", + }, + }, + "topHits": Object { + "top_hits": Object { + "size": 100, + "sort": Array [ + Object { + "kibana.combined_timestamp": Object { + "order": "asc", + "unmapped_type": "date", + }, + }, + ], + }, + }, + }, + "composite": Object { + "size": 100, + "sources": Array [ + Object { + "host.name": Object { + "terms": Object { + "field": "host.name", + }, + }, + }, + ], + }, + }, +} +`; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/__snapshots__/group_and_bulk_create.test.ts.snap b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/__snapshots__/group_and_bulk_create.test.ts.snap new file mode 100644 index 00000000000000..05674205e1e380 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/__snapshots__/group_and_bulk_create.test.ts.snap @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`groupAndBulkCreate utils buildBucketHistoryFilter should create the expected query 1`] = ` +Array [ + Object { + "bool": Object { + "must_not": Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "host.name": "host-0", + }, + }, + Object { + "term": Object { + "source.ip": "127.0.0.1", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "gte": "2022-11-01T11:30:00.000Z", + "lte": "2022-11-01T12:00:00Z", + }, + }, + }, + ], + }, + }, + Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "host.name": "host-1", + }, + }, + Object { + "term": Object { + "source.ip": "192.0.0.1", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "strict_date_optional_time", + "gte": "2022-11-01T11:30:00.000Z", + "lte": "2022-11-01T12:05:00Z", + }, + }, + }, + ], + }, + }, + ], + }, + }, +] +`; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/build_group_by_field_aggregation.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/build_group_by_field_aggregation.test.ts new file mode 100644 index 00000000000000..010a2ab50ffabf --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/build_group_by_field_aggregation.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { buildGroupByFieldAggregation } from './build_group_by_field_aggregation'; + +describe('build_group_by_field_aggregation', () => { + it('Build Group-by-field aggregation', () => { + const groupByFields = ['host.name']; + const maxSignals = 100; + + const agg = buildGroupByFieldAggregation({ + groupByFields, + maxSignals, + aggregatableTimestampField: 'kibana.combined_timestamp', + }); + expect(agg).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/build_group_by_field_aggregation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/build_group_by_field_aggregation.ts new file mode 100644 index 00000000000000..4df370d6bced91 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/build_group_by_field_aggregation.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. + */ + +interface GetGroupByFieldAggregationArgs { + groupByFields: string[]; + maxSignals: number; + aggregatableTimestampField: string; +} + +export const buildGroupByFieldAggregation = ({ + groupByFields, + maxSignals, + aggregatableTimestampField, +}: GetGroupByFieldAggregationArgs) => ({ + eventGroups: { + composite: { + sources: groupByFields.map((field) => ({ + [field]: { + terms: { + field, + }, + }, + })), + size: maxSignals, + }, + aggs: { + topHits: { + top_hits: { + size: maxSignals, + sort: [ + { + [aggregatableTimestampField]: { + order: 'asc' as const, + unmapped_type: 'date', + }, + }, + ], + }, + }, + max_timestamp: { + max: { + field: aggregatableTimestampField, + }, + }, + min_timestamp: { + min: { + field: aggregatableTimestampField, + }, + }, + }, + }, +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/group_and_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/group_and_bulk_create.test.ts new file mode 100644 index 00000000000000..0a8fe775bb335d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/group_and_bulk_create.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 moment from 'moment'; +import { buildBucketHistoryFilter, filterBucketHistory } from './group_and_bulk_create'; +import type { BucketHistory } from './group_and_bulk_create'; + +describe('groupAndBulkCreate utils', () => { + const bucketHistory: BucketHistory[] = [ + { + key: { + 'host.name': 'host-0', + 'source.ip': '127.0.0.1', + }, + endDate: '2022-11-01T12:00:00Z', + }, + { + key: { + 'host.name': 'host-1', + 'source.ip': '192.0.0.1', + }, + endDate: '2022-11-01T12:05:00Z', + }, + ]; + + it('buildBucketHistoryFilter should create the expected query', () => { + const from = moment('2022-11-01T11:30:00Z'); + + const filter = buildBucketHistoryFilter({ + bucketHistory, + primaryTimestamp: '@timestamp', + secondaryTimestamp: undefined, + from, + }); + + expect(filter).toMatchSnapshot(); + }); + + it('filterBucketHistory should remove outdated buckets', () => { + const fromDate = new Date('2022-11-01T12:02:00Z'); + + const filteredBuckets = filterBucketHistory({ bucketHistory, fromDate }); + + expect(filteredBuckets).toEqual([ + { + key: { + 'host.name': 'host-1', + 'source.ip': '192.0.0.1', + }, + endDate: '2022-11-01T12:05:00Z', + }, + ]); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/group_and_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/group_and_bulk_create.ts new file mode 100644 index 00000000000000..690b0f698f3066 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/alert_suppression/group_and_bulk_create.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. + */ + +import type moment from 'moment'; + +import type * as estypes from '@elastic/elasticsearch/lib/api/types'; + +import { withSecuritySpan } from '../../../../utils/with_security_span'; +import { buildTimeRangeFilter } from '../build_events_query'; +import type { + EventGroupingMultiBucketAggregationResult, + GroupAndBulkCreateParams, + GroupAndBulkCreateReturnType, +} from '../types'; +import { addToSearchAfterReturn, getUnprocessedExceptionsWarnings } from '../utils'; +import type { SuppressionBuckets } from '../../rule_types/factories/utils/wrap_suppressed_alerts'; +import { wrapSuppressedAlerts } from '../../rule_types/factories/utils/wrap_suppressed_alerts'; +import { buildGroupByFieldAggregation } from './build_group_by_field_aggregation'; +import { singleSearchAfter } from '../single_search_after'; + +export interface BucketHistory { + key: Record; + endDate: string; +} + +/** + * Builds a filter that excludes documents from existing buckets. + */ +export const buildBucketHistoryFilter = ({ + bucketHistory, + primaryTimestamp, + secondaryTimestamp, + from, +}: { + bucketHistory: BucketHistory[]; + primaryTimestamp: string; + secondaryTimestamp: string | undefined; + from: moment.Moment; +}): estypes.QueryDslQueryContainer[] | undefined => { + if (bucketHistory.length === 0) { + return undefined; + } + return [ + { + bool: { + must_not: bucketHistory.map((bucket) => ({ + bool: { + filter: [ + ...Object.entries(bucket.key).map(([field, value]) => ({ + term: { + [field]: value, + }, + })), + buildTimeRangeFilter({ + to: bucket.endDate, + from: from.toISOString(), + primaryTimestamp, + secondaryTimestamp, + }), + ], + }, + })), + }, + }, + ]; +}; + +export const filterBucketHistory = ({ + bucketHistory, + fromDate, +}: { + bucketHistory: BucketHistory[]; + fromDate: Date; +}) => { + return bucketHistory.filter((bucket) => new Date(bucket.endDate) > fromDate); +}; + +export const groupAndBulkCreate = async ({ + runOpts, + services, + spaceId, + filter, + buildReasonMessage, + bucketHistory, + groupByFields, +}: GroupAndBulkCreateParams): Promise => { + return withSecuritySpan('groupAndBulkCreate', async () => { + const tuple = runOpts.tuple; + + const filteredBucketHistory = filterBucketHistory({ + bucketHistory: bucketHistory ?? [], + fromDate: tuple.from.toDate(), + }); + + const toReturn: GroupAndBulkCreateReturnType = { + success: true, + warning: false, + searchAfterTimes: [], + enrichmentTimes: [], + bulkCreateTimes: [], + lastLookBackDate: null, + createdSignalsCount: 0, + createdSignals: [], + errors: [], + warningMessages: [], + state: { + suppressionGroupHistory: filteredBucketHistory, + }, + }; + + const exceptionsWarning = getUnprocessedExceptionsWarnings(runOpts.unprocessedExceptions); + if (exceptionsWarning) { + toReturn.warningMessages.push(exceptionsWarning); + } + + try { + if (groupByFields.length === 0) { + throw new Error('groupByFields length must be greater than 0'); + } + + const bucketHistoryFilter = buildBucketHistoryFilter({ + bucketHistory: filteredBucketHistory, + primaryTimestamp: runOpts.primaryTimestamp, + secondaryTimestamp: runOpts.secondaryTimestamp, + from: tuple.from, + }); + + const groupingAggregation = buildGroupByFieldAggregation({ + groupByFields, + maxSignals: tuple.maxSignals, + aggregatableTimestampField: runOpts.aggregatableTimestampField, + }); + + const { searchResult, searchDuration, searchErrors } = await singleSearchAfter({ + aggregations: groupingAggregation, + searchAfterSortIds: undefined, + index: runOpts.inputIndex, + from: tuple.from.toISOString(), + to: tuple.to.toISOString(), + services, + ruleExecutionLogger: runOpts.ruleExecutionLogger, + filter, + pageSize: 0, + primaryTimestamp: runOpts.primaryTimestamp, + secondaryTimestamp: runOpts.secondaryTimestamp, + runtimeMappings: runOpts.runtimeMappings, + additionalFilters: bucketHistoryFilter, + }); + toReturn.searchAfterTimes.push(searchDuration); + toReturn.errors.push(...searchErrors); + + const eventsByGroupResponseWithAggs = + searchResult as EventGroupingMultiBucketAggregationResult; + if (!eventsByGroupResponseWithAggs.aggregations) { + throw new Error('expected to find aggregations on search result'); + } + + const buckets = eventsByGroupResponseWithAggs.aggregations.eventGroups.buckets; + + if (buckets.length === 0) { + return toReturn; + } + + const suppressionBuckets: SuppressionBuckets[] = buckets.map((bucket) => ({ + event: bucket.topHits.hits.hits[0], + count: bucket.doc_count, + start: bucket.min_timestamp.value_as_string + ? new Date(bucket.min_timestamp.value_as_string) + : tuple.from.toDate(), + end: bucket.max_timestamp.value_as_string + ? new Date(bucket.max_timestamp.value_as_string) + : tuple.to.toDate(), + terms: Object.entries(bucket.key).map(([key, value]) => ({ field: key, value })), + })); + + const wrappedAlerts = wrapSuppressedAlerts({ + suppressionBuckets, + spaceId, + completeRule: runOpts.completeRule, + mergeStrategy: runOpts.mergeStrategy, + indicesToQuery: runOpts.inputIndex, + buildReasonMessage, + alertTimestampOverride: runOpts.alertTimestampOverride, + }); + + const bulkCreateResult = await runOpts.bulkCreate(wrappedAlerts); + + addToSearchAfterReturn({ current: toReturn, next: bulkCreateResult }); + + runOpts.ruleExecutionLogger.debug(`created ${bulkCreateResult.createdItemsCount} signals`); + + const newBucketHistory: BucketHistory[] = buckets + .filter((bucket) => { + return !Object.values(bucket.key).includes(null); + }) + .map((bucket) => { + return { + // This cast should be safe as we just filtered out buckets where any key has a null value. + key: bucket.key as Record, + endDate: bucket.max_timestamp.value_as_string + ? bucket.max_timestamp.value_as_string + : tuple.to.toISOString(), + }; + }); + + toReturn.state.suppressionGroupHistory.push(...newBucketHistory); + } catch (exc) { + toReturn.success = false; + toReturn.errors.push(exc.message); + } + + return toReturn; + }); +}; 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 a5a8c4963f227d..fdc53d6905a0bc 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 @@ -26,6 +26,7 @@ interface BuildEventsSearchQuery { primaryTimestamp: TimestampOverride; secondaryTimestamp: TimestampOverride | undefined; trackTotalHits?: boolean; + additionalFilters?: estypes.QueryDslQueryContainer[]; } interface BuildEqlSearchRequestParams { @@ -44,7 +45,7 @@ interface BuildEqlSearchRequestParams { exceptionFilter: Filter | undefined; } -const buildTimeRangeFilter = ({ +export const buildTimeRangeFilter = ({ to, from, primaryTimestamp, @@ -130,6 +131,7 @@ export const buildEventsSearchQuery = ({ primaryTimestamp, secondaryTimestamp, trackTotalHits, + additionalFilters, }: BuildEventsSearchQuery) => { const timestamps = secondaryTimestamp ? [primaryTimestamp, secondaryTimestamp] @@ -146,7 +148,11 @@ export const buildEventsSearchQuery = ({ secondaryTimestamp, }); - const filterWithTime: estypes.QueryDslQueryContainer[] = [filter, rangeFilter]; + const filterWithTime: estypes.QueryDslQueryContainer[] = [ + filter, + rangeFilter, + ...(additionalFilters ? additionalFilters : []), + ]; const sort: estypes.Sort = []; sort.push({ 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 52c0247b950db1..7c3dc31b742ae0 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 @@ -5,71 +5,49 @@ * 2.0. */ -import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type { AlertInstanceContext, AlertInstanceState, RuleExecutorServices, } from '@kbn/alerting-plugin/server'; -import type { ListClient } from '@kbn/lists-plugin/server'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { firstValueFrom } from 'rxjs'; import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; -import type { Filter } from '@kbn/es-query'; import { getFilter } from '../get_filter'; +import type { BucketHistory } from '../alert_suppression/group_and_bulk_create'; +import { groupAndBulkCreate } from '../alert_suppression/group_and_bulk_create'; 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 '../../rule_schema'; +import type { UnifiedQueryRuleParams } from '../../rule_schema'; import type { ExperimentalFeatures } from '../../../../../common/experimental_features'; import { buildReasonMessageForQueryAlert } from '../reason_formatters'; import { withSecuritySpan } from '../../../../utils/with_security_span'; -import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring'; import { scheduleNotificationResponseActions } from '../../rule_response_actions/schedule_notification_response_actions'; import type { SetupPlugins } from '../../../../plugin_contract'; +import type { RunOpts } from '../../rule_types/types'; export const queryExecutor = async ({ - inputIndex, - runtimeMappings, - completeRule, - tuple, - listClient, + runOpts, experimentalFeatures, - ruleExecutionLogger, eventsTelemetry, services, version, - searchAfterSize, - bulkCreate, - wrapHits, - primaryTimestamp, - secondaryTimestamp, - unprocessedExceptions, - exceptionFilter, + spaceId, + bucketHistory, osqueryCreateAction, licensing, }: { - inputIndex: string[]; - runtimeMappings: estypes.MappingRuntimeFields | undefined; - completeRule: CompleteRule; - tuple: RuleRangeTuple; - listClient: ListClient; + runOpts: RunOpts; experimentalFeatures: ExperimentalFeatures; - ruleExecutionLogger: IRuleExecutionLogForExecutors; eventsTelemetry: ITelemetryEventsSender | undefined; services: RuleExecutorServices; version: string; - searchAfterSize: number; - bulkCreate: BulkCreate; - wrapHits: WrapHits; - primaryTimestamp: string; - secondaryTimestamp?: string; - unprocessedExceptions: ExceptionListItemSchema[]; - exceptionFilter: Filter | undefined; + spaceId: string; + bucketHistory?: BucketHistory[]; osqueryCreateAction: SetupPlugins['osquery']['osqueryCreateAction']; licensing: LicensingPluginSetup; }) => { + const completeRule = runOpts.completeRule; const ruleParams = completeRule.ruleParams; return withSecuritySpan('queryExecutor', async () => { @@ -80,31 +58,46 @@ export const queryExecutor = async ({ query: ruleParams.query, savedId: ruleParams.savedId, services, - index: inputIndex, - exceptionFilter, - }); - - const result = await searchAfterAndBulkCreate({ - tuple, - exceptionsList: unprocessedExceptions, - services, - listClient, - ruleExecutionLogger, - eventsTelemetry, - inputIndexPattern: inputIndex, - pageSize: searchAfterSize, - filter: esFilter, - buildReasonMessage: buildReasonMessageForQueryAlert, - bulkCreate, - wrapHits, - runtimeMappings, - primaryTimestamp, - secondaryTimestamp, + index: runOpts.inputIndex, + exceptionFilter: runOpts.exceptionFilter, }); const license = await firstValueFrom(licensing.license$); + const hasPlatinumLicense = license.hasAtLeast('platinum'); const hasGoldLicense = license.hasAtLeast('gold'); + const result = + ruleParams.alertSuppression?.groupBy != null && hasPlatinumLicense + ? await groupAndBulkCreate({ + runOpts, + services, + spaceId, + filter: esFilter, + buildReasonMessage: buildReasonMessageForQueryAlert, + bucketHistory, + groupByFields: ruleParams.alertSuppression.groupBy, + }) + : { + ...(await searchAfterAndBulkCreate({ + tuple: runOpts.tuple, + exceptionsList: runOpts.unprocessedExceptions, + services, + listClient: runOpts.listClient, + ruleExecutionLogger: runOpts.ruleExecutionLogger, + eventsTelemetry, + inputIndexPattern: runOpts.inputIndex, + pageSize: runOpts.searchAfterSize, + filter: esFilter, + buildReasonMessage: buildReasonMessageForQueryAlert, + bulkCreate: runOpts.bulkCreate, + wrapHits: runOpts.wrapHits, + runtimeMappings: runOpts.runtimeMappings, + primaryTimestamp: runOpts.primaryTimestamp, + secondaryTimestamp: runOpts.secondaryTimestamp, + })), + state: {}, + }; + if (hasGoldLicense) { if (completeRule.ruleParams.responseActions?.length && result.createdSignalsCount) { scheduleNotificationResponseActions( 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 04fec0e21a4678..e1ef6e58678594 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 @@ -33,6 +33,7 @@ interface SingleSearchAfterParams { secondaryTimestamp: TimestampOverride | undefined; trackTotalHits?: boolean; runtimeMappings: estypes.MappingRuntimeFields | undefined; + additionalFilters?: estypes.QueryDslQueryContainer[]; } // utilize search_after for paging results into bulk. @@ -53,6 +54,7 @@ export const singleSearchAfter = async < primaryTimestamp, secondaryTimestamp, trackTotalHits, + additionalFilters, }: SingleSearchAfterParams): Promise<{ searchResult: SignalSearchResponse; searchDuration: string; @@ -73,6 +75,7 @@ export const singleSearchAfter = async < primaryTimestamp, secondaryTimestamp, trackTotalHits, + additionalFilters, }); const start = performance.now(); 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 e8dd19fd2ff8ef..af354baa679038 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 @@ -7,6 +7,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type moment from 'moment'; +import type { ESSearchResponse } from '@kbn/es-types'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type { RuleTypeState, @@ -26,7 +27,7 @@ import type { EqlSequence, } from '../../../../common/detection_engine/types'; import type { ITelemetryEventsSender } from '../../telemetry/sender'; -import type { RuleParams } from '../rule_schema'; +import type { RuleParams, UnifiedQueryRuleParams } from '../rule_schema'; import type { GenericBulkCreateResponse } from '../rule_types/factories'; import type { BuildReasonMessage } from './reason_formatters'; import type { @@ -35,8 +36,11 @@ import type { WrappedFieldsLatest, } from '../../../../common/detection_engine/schemas/alerts'; import type { IRuleExecutionLogForExecutors } from '../rule_monitoring'; +import type { buildGroupByFieldAggregation } from './alert_suppression/build_group_by_field_aggregation'; import type { RuleResponse } from '../../../../common/detection_engine/rule_schema'; import type { EnrichEvents } from './enrichments/types'; +import type { BucketHistory } from './alert_suppression/group_and_bulk_create'; +import type { RunOpts } from '../rule_types/types'; export interface ThresholdResult { terms?: Array<{ @@ -282,6 +286,16 @@ export interface SearchAfterAndBulkCreateParams { secondaryTimestamp?: string; } +export interface GroupAndBulkCreateParams { + runOpts: RunOpts; + services: RuleServices; + spaceId: string; + filter: estypes.QueryDslQueryContainer; + buildReasonMessage: BuildReasonMessage; + bucketHistory?: BucketHistory[]; + groupByFields: string[]; +} + export interface SearchAfterAndBulkCreateReturnType { success: boolean; warning: boolean; @@ -295,6 +309,12 @@ export interface SearchAfterAndBulkCreateReturnType { warningMessages: string[]; } +export interface GroupAndBulkCreateReturnType extends SearchAfterAndBulkCreateReturnType { + state: { + suppressionGroupHistory: BucketHistory[]; + }; +} + export interface MultiAggBucket { cardinality?: Array<{ field: string; @@ -313,3 +333,12 @@ export interface ThresholdAlertState extends RuleTypeState { initialized: boolean; signalHistory: ThresholdSignalHistory; } + +export type EventGroupingMultiBucketAggregationResult = ESSearchResponse< + SignalSource, + { + body: { + aggregations: ReturnType; + }; + } +>; diff --git a/x-pack/plugins/security_solution/server/lib/risk_score/readme.md b/x-pack/plugins/security_solution/server/lib/risk_score/readme.md index e2bc1c280affc5..17087d447e6308 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_score/readme.md +++ b/x-pack/plugins/security_solution/server/lib/risk_score/readme.md @@ -1,3 +1,13 @@ + +# Version +|Version|Risk Score Entity|Scripts created|Ingest pipelines created|Transforms created|Behind feature flag|Notes| +|-------|------|-------|----------------|----------|----|----| +|8.3`deprecated`|host|1.ml_hostriskscore_levels_script_{spacename} 2.ml_hostriskscore_map_script_{spacename} 3.ml_hostriskscore_reduce_script_{spacename} 4.ml_hostriskscore_init_script_{spacename}|ml_hostriskscore_ingest_pipeline_{spacename}|1.ml_hostriskscore_pivot_transform_{spacename} Destination Index: `ml_host_risk_score_{spacename}` 2.ml_hostriskscore_latest_transform_{spacename} Destination Index: `ml_host_risk_score_latest_{spacename}`| Yes|https://github.com/elastic/detection-rules/blob/main/docs/experimental-machine-learning/host-risk-score.md| +|8.3`deprecated`|user|1.ml_userriskscore_levels_script_{spacename} 2.ml_userriskscore_map_script_{spacename} 3.ml_userriskscore_reduce_script_{spacename}|ml_userriskscore_ingest_pipeline_{spacename}|1.ml_userriskscore_pivot_transform_{spacename} Destination index: `ml_user_risk_score_{spacename}` 2.ml_userriskscore_latest_transform_{spacename} Destination index: `ml_user_risk_score_latest_{spacename}`|Yes|https://github.com/elastic/detection-rules/blob/main/docs/experimental-machine-learning/user-risk-score.md| +|8.4`deprecated`|host|1.ml_hostriskscore_levels_script 2.ml_hostriskscore_map_script 3.ml_hostriskscore_reduce_script 4.ml_hostriskscore_init_script|ml_hostriskscore_ingest_pipeline|1.ml_hostriskscore_pivot_transform_{spacename} Destination Index: `ml_host_risk_score_{spacename}` 2.ml_hostriskscore_latest_transform_{spacename} Destination Index: `ml_host_risk_score_latest_{spacename}`|Yes|Installation via dev tools releasesd. https://github.com/elastic/kibana/blob/8.4/x-pack/plugins/security_solution/server/lib/prebuilt_dev_tool_content/console_templates/enable_host_risk_score.console| +|8.4`deprecated`|user|1.ml_userriskscore_levels_script_{spacename} 2.ml_userriskscore_map_script_{spacename} 3.ml_userriskscore_reduce_script_{spacename}|ml_userriskscore_ingest_pipeline_{spacename}|1.ml_userriskscore_pivot_transform_{spacename} Destination index: `ml_user_risk_score_{spacename}` 2.ml_userriskscore_latest_transform_{spacename} Destination index: `ml_user_risk_score_latest_{spacename}`|Yes|Installation via dev tools not available yet (Installation via dev tools is availble in 8.5). +|8.5+|host|1.ml_hostriskscore_levels_script_{spacename} 2.ml_hostriskscore_map_script_{spacename} 3.ml_hostriskscore_reduce_script_{spacename} 4.ml_hostriskscore_init_script_{spacename}|ml_hostriskscore_ingest_pipeline_{spacename}|1.ml_hostriskscore_pivot_transform_{spacename} Destination Index: `ml_host_risk_score_{spacename}` 2.ml_hostriskscore_latest_transform_{spacename} Destination Index: `ml_host_risk_score_latest_{spacename}`| No|`Breaking Chang`: New schema for Destination indices| +|8.5+|user|1.ml_userriskscore_levels_script_{spacename} 2.ml_userriskscore_map_script_{spacename} 3.ml_userriskscore_reduce_script_{spacename}|ml_userriskscore_ingest_pipeline_{spacename}|1.ml_userriskscore_pivot_transform_{spacename} Destination index: `ml_user_risk_score_{spacename}` 2.ml_userriskscore_latest_transform_{spacename} Destination index: `ml_user_risk_score_latest_{spacename}`|No|`Breaking Chang`: New schema for Destination indices| # Risk Score API ### API usage diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index dd4993807f7c3a..a4d9610b774baa 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -36,7 +36,6 @@ import { createMlAlertType, createNewTermsAlertType, createQueryAlertType, - createSavedQueryAlertType, createThresholdAlertType, } from './lib/detection_engine/rule_types'; import { initRoutes } from './routes'; @@ -260,7 +259,12 @@ export class Plugin implements ISecuritySolutionPlugin { plugins.alerting.registerType(securityRuleTypeWrapper(createEqlAlertType(ruleOptions))); plugins.alerting.registerType( securityRuleTypeWrapper( - createSavedQueryAlertType({ ...ruleOptions, ...queryRuleAdditionalOptions }) + createQueryAlertType({ + ...ruleOptions, + ...queryRuleAdditionalOptions, + id: SAVED_QUERY_RULE_TYPE_ID, + name: 'Saved Query Rule', + }) ) ); plugins.alerting.registerType( @@ -269,7 +273,12 @@ export class Plugin implements ISecuritySolutionPlugin { plugins.alerting.registerType(securityRuleTypeWrapper(createMlAlertType(ruleOptions))); plugins.alerting.registerType( securityRuleTypeWrapper( - createQueryAlertType({ ...ruleOptions, ...queryRuleAdditionalOptions }) + createQueryAlertType({ + ...ruleOptions, + ...queryRuleAdditionalOptions, + id: QUERY_RULE_TYPE_ID, + name: 'Custom Query Rule', + }) ) ); plugins.alerting.registerType(securityRuleTypeWrapper(createThresholdAlertType(ruleOptions))); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/close_alert.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/close_alert.tsx index 82b0b5c061f771..8bca3b4a05a9a3 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/close_alert.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/close_alert.tsx @@ -40,6 +40,7 @@ const AdditionalOptions: React.FC = ({ data-test-subj="opsgenie-source-row" fullWidth label={i18n.SOURCE_FIELD_LABEL} + helpText={i18n.OPSGENIE_SOURCE_HELP} > = ({ - + = ({ error={errors['subActionParams.alias']} isInvalid={isAliasInvalid} label={i18n.ALIAS_REQUIRED_FIELD_LABEL} + helpText={i18n.OPSGENIE_ALIAS_HELP} > = ({ data-test-subj="opsgenie-entity-row" fullWidth label={i18n.ENTITY_FIELD_LABEL} + helpText={i18n.OPSGENIE_ENTITY_HELP} > = ({ data-test-subj="opsgenie-source-row" fullWidth label={i18n.SOURCE_FIELD_LABEL} + helpText={i18n.OPSGENIE_SOURCE_HELP} > = ({ - + = ({ inputTargetValue={subActionParams?.description} label={i18n.DESCRIPTION_FIELD_LABEL} /> - + { it('returns merged filters and user search if neither is empty', () => { expect(combineFiltersAndUserSearch('monitor.id:foo', 'monitor.name:bar')).toEqual( - '(monitor.id:foo) and (monitor.name:bar)' + '(monitor.id:foo) AND (monitor.name:bar)' ); }); }); diff --git a/x-pack/plugins/synthetics/common/lib/combine_filters_and_user_search.ts b/x-pack/plugins/synthetics/common/lib/combine_filters_and_user_search.ts index 30a6601f1b6300..a890207867b2a4 100644 --- a/x-pack/plugins/synthetics/common/lib/combine_filters_and_user_search.ts +++ b/x-pack/plugins/synthetics/common/lib/combine_filters_and_user_search.ts @@ -11,5 +11,5 @@ export const combineFiltersAndUserSearch = (filters: string, search: string) => } if (!filters) return search; if (!search) return filters; - return `(${filters}) and (${search})`; + return `(${filters}) AND (${search})`; }; diff --git a/x-pack/plugins/synthetics/common/lib/stringify_kueries.test.ts b/x-pack/plugins/synthetics/common/lib/stringify_kueries.test.ts index f3fc3d3c95cfbe..e37b69eee5bf55 100644 --- a/x-pack/plugins/synthetics/common/lib/stringify_kueries.test.ts +++ b/x-pack/plugins/synthetics/common/lib/stringify_kueries.test.ts @@ -16,49 +16,61 @@ describe('stringifyKueries', () => { }); it('stringifies the current values', () => { - expect(stringifyKueries(kueries)).toMatchSnapshot(); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"foo: (fooValue1 OR fooValue2) AND bar: barValue"` + ); }); it('correctly stringifies a single value', () => { kueries = new Map(); kueries.set('foo', ['fooValue']); - expect(stringifyKueries(kueries)).toMatchSnapshot(); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot(`"foo: fooValue"`); }); it('returns an empty string for an empty map', () => { - expect(stringifyKueries(new Map())).toMatchSnapshot(); + expect(stringifyKueries(new Map())).toMatchInlineSnapshot(`""`); }); it('returns an empty string for an empty value', () => { kueries = new Map(); kueries.set('aField', ['']); - expect(stringifyKueries(kueries)).toMatchSnapshot(); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot(`""`); }); it('adds quotations if the value contains a space', () => { kueries.set('baz', ['baz value']); - expect(stringifyKueries(kueries)).toMatchSnapshot(); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"foo: (fooValue1 OR fooValue2) AND bar: barValue AND baz: \\"baz value\\""` + ); }); it('adds quotations inside parens if there are values containing spaces', () => { kueries.set('foo', ['foo value 1', 'foo value 2']); - expect(stringifyKueries(kueries)).toMatchSnapshot(); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"foo: (\\"foo value 1\\" OR \\"foo value 2\\") AND bar: barValue"` + ); }); it('handles parens for values with greater than 2 items', () => { kueries.set('foo', ['val1', 'val2', 'val3']); kueries.set('baz', ['baz 1', 'baz 2']); - expect(stringifyKueries(kueries)).toMatchSnapshot(); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"foo: (val1 OR val2 OR val3) AND bar: barValue AND baz: (\\"baz 1\\" OR \\"baz 2\\")"` + ); }); it('handles number values', () => { kueries.set('port', [80, 8080, 443]); - expect(stringifyKueries(kueries)).toMatchSnapshot(); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"foo: (fooValue1 OR fooValue2) AND bar: barValue AND port: (80 OR 8080 OR 443)"` + ); }); it('handles colon characters in values', () => { kueries.set('monitor.id', ['https://elastic.co', 'https://example.com']); - expect(stringifyKueries(kueries)).toMatchSnapshot(); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"foo: (fooValue1 OR fooValue2) AND bar: barValue AND monitor.id: (\\"https://elastic.co\\" OR \\"https://example.com\\")"` + ); }); it('handles precending empty array', () => { @@ -71,21 +83,61 @@ describe('stringifyKueries', () => { }) ); expect(stringifyKueries(kueries)).toMatchInlineSnapshot( - `"(observer.geo.name:us-east or observer.geo.name:apj or observer.geo.name:sydney or observer.geo.name:us-west)"` + `"observer.geo.name: (us-east OR apj OR sydney OR us-west)"` ); }); it('handles skipped empty arrays', () => { kueries = new Map( Object.entries({ - tags: [], + tags: ['tag1', 'tag2'], 'monitor.type': ['http'], 'url.port': [], 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], }) ); expect(stringifyKueries(kueries)).toMatchInlineSnapshot( - `"monitor.type:http and (observer.geo.name:us-east or observer.geo.name:apj or observer.geo.name:sydney or observer.geo.name:us-west)"` + `"tags: (tag1 OR tag2) AND monitor.type: http AND observer.geo.name: (us-east OR apj OR sydney OR us-west)"` + ); + }); + + it('handles tags AND logic', () => { + kueries = new Map( + Object.entries({ + 'monitor.type': ['http'], + 'url.port': [], + 'observer.geo.name': ['us-east', 'apj', 'sydney', 'us-west'], + tags: ['tag1', 'tag2'], + }) + ); + expect(stringifyKueries(kueries, true)).toMatchInlineSnapshot( + `"monitor.type: http AND observer.geo.name: (us-east OR apj OR sydney OR us-west) AND tags: (tag1 AND tag2)"` + ); + }); + + it('handles tags AND logic with only tags', () => { + kueries = new Map( + Object.entries({ + 'monitor.type': [], + 'url.port': [], + 'observer.geo.name': [], + tags: ['tag1', 'tag2'], + }) + ); + expect(stringifyKueries(kueries, true)).toMatchInlineSnapshot(`"tags: (tag1 AND tag2)"`); + }); + + it('handles values with spaces', () => { + kueries = new Map( + Object.entries({ + tags: ['Weird tag'], + 'monitor.type': ['http'], + 'url.port': [], + 'observer.geo.name': ['us east', 'apj', 'sydney', 'us-west'], + }) + ); + expect(stringifyKueries(kueries)).toMatchInlineSnapshot( + `"tags: \\"Weird tag\\" AND monitor.type: http AND observer.geo.name: (\\"us east\\" OR apj OR sydney OR us-west)"` ); }); }); diff --git a/x-pack/plugins/synthetics/common/lib/stringify_kueries.ts b/x-pack/plugins/synthetics/common/lib/stringify_kueries.ts index 86497f63864f5d..3860dcd8485210 100644 --- a/x-pack/plugins/synthetics/common/lib/stringify_kueries.ts +++ b/x-pack/plugins/synthetics/common/lib/stringify_kueries.ts @@ -10,28 +10,31 @@ * The strings contain all of the values chosen for the given field (which is also the key value). * Reduce the list of query strings to a singular string, with AND operators between. */ -export const stringifyKueries = (kueries: Map>): string => - Array.from(kueries.keys()) + +export const stringifyKueries = ( + kueries: Map>, + logicalANDForTag?: boolean +): string => { + const defaultCondition = 'OR'; + + return Array.from(kueries.keys()) .map((key) => { - const value = kueries.get(key); + let condition = defaultCondition; + if (key === 'tags' && logicalANDForTag) { + condition = 'AND'; + } + const value = kueries.get(key)?.filter((v) => v !== ''); if (!value || value.length === 0) return ''; - return value.reduce( - (prev: string, cur: string | number, index: number, array: Array) => { - let expression: string = `${key}:${cur}`; - if (typeof cur !== 'number' && (cur.indexOf(' ') >= 0 || cur.indexOf(':') >= 0)) { - expression = `${key}:"${cur}"`; - } - if (array.length === 1) { - return expression; - } else if (array.length > 1 && index === 0) { - return `(${expression}`; - } else if (index + 1 === array.length) { - return `${prev} or ${expression})`; - } - return `${prev} or ${expression}`; - }, - '' - ); + + if (value.length === 1) { + return isAlphaNumeric(value[0] as string) ? `${key}: ${value[0]}` : `${key}: "${value[0]}"`; + } + + const values = value + .map((v) => (isAlphaNumeric(v as string) ? v : `"${v}"`)) + .join(` ${condition} `); + + return `${key}: (${values})`; }) .reduce((prev, cur, index, array) => { if (array.length === 1 || index === 0) { @@ -41,5 +44,11 @@ export const stringifyKueries = (kueries: Map>): } else if (prev === '' && !!cur) { return cur; } - return `${prev} and ${cur}`; + return `${prev} AND ${cur}`; }, ''); +}; + +const isAlphaNumeric = (str: string) => { + const format = /[ `!@#$%^&*()_+=\[\]{};':"\\|,.<>\/?~]/; + return !format.test(str); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_status.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_status.tsx new file mode 100644 index 00000000000000..41cc80bb73bb25 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/monitor_status.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiBadge, EuiDescriptionList, EuiLoadingSpinner } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EncryptedSyntheticsMonitor } from '../../../../../../common/runtime_types'; + +export const MonitorStatus = ({ + loading, + monitor, + status, + compressed = true, +}: { + loading?: boolean; + compressed?: boolean; + monitor: EncryptedSyntheticsMonitor; + status?: string; +}) => { + const isBrowserType = monitor.type === 'browser'; + + const badge = loading ? ( + + ) : !status ? ( + {PENDING_LABEL} + ) : status === 'up' ? ( + {isBrowserType ? SUCCESS_LABEL : UP_LABEL} + ) : ( + {isBrowserType ? FAILED_LABEL : DOWN_LABEL} + ); + + return ( + + ); +}; + +const STATUS_LABEL = i18n.translate('xpack.synthetics.monitorStatus.statusLabel', { + defaultMessage: 'Status', +}); + +const FAILED_LABEL = i18n.translate('xpack.synthetics.monitorStatus.failedLabel', { + defaultMessage: 'Failed', +}); + +const PENDING_LABEL = i18n.translate('xpack.synthetics.monitorStatus.pendingLabel', { + defaultMessage: 'Pending', +}); + +const SUCCESS_LABEL = i18n.translate('xpack.synthetics.monitorStatus.succeededLabel', { + defaultMessage: 'Succeeded', +}); + +const UP_LABEL = i18n.translate('xpack.synthetics.monitorStatus.upLabel', { + defaultMessage: 'Up', +}); + +const DOWN_LABEL = i18n.translate('xpack.synthetics.monitorStatus.downLabel', { + defaultMessage: 'Down', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx index 35e797fe50a23d..b1c2fd4ea2e5f1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx @@ -11,9 +11,14 @@ import { EuiButton, EuiLink, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useFormContext } from 'react-hook-form'; import { useFetcher, FETCH_STATUS } from '@kbn/observability-plugin/public'; -import { SyntheticsMonitor } from '../types'; +import { DeleteMonitor } from '../../monitors_page/management/monitor_list_table/delete_monitor'; +import { ConfigKey, SourceType, SyntheticsMonitor } from '../types'; import { format } from './formatter'; -import { createMonitorAPI, updateMonitorAPI } from '../../../state/monitor_management/api'; +import { + createMonitorAPI, + getMonitorAPI, + updateMonitorAPI, +} from '../../../state/monitor_management/api'; import { kibanaService } from '../../../../../utils/kibana_service'; import { MONITORS_ROUTE, MONITOR_EDIT_ROUTE } from '../../../../../../common/constants'; @@ -28,8 +33,17 @@ export const ActionBar = () => { formState: { errors }, } = useFormContext(); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const [monitorData, setMonitorData] = useState(undefined); + const { data: monitorObject } = useFetcher(() => { + if (isEdit) { + return getMonitorAPI({ id: monitorId }); + } + return undefined; + }, []); + const { data, status } = useFetcher(() => { if (!monitorData) { return null; @@ -71,26 +85,51 @@ export const ActionBar = () => { return status === FETCH_STATUS.SUCCESS ? ( ) : ( - - - {CANCEL_LABEL} - - - - - - {isEdit ? UPDATE_MONITOR_LABEL : CREATE_MONITOR_LABEL} - - - - - + <> + + + {isEdit && ( +
+ { + setIsDeleteModalVisible(true); + }} + > + {DELETE_MONITOR_LABEL} + +
+ )} +
+ + {CANCEL_LABEL} + + + + {isEdit ? UPDATE_MONITOR_LABEL : CREATE_MONITOR_LABEL} + + +
+ {isDeleteModalVisible && ( + { + history.push(MONITORS_ROUTE); + }} + isProjectMonitor={ + monitorObject?.attributes?.[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT + } + setIsDeleteModalVisible={setIsDeleteModalVisible} + /> + )} + ); }; @@ -105,6 +144,13 @@ const CREATE_MONITOR_LABEL = i18n.translate( } ); +const DELETE_MONITOR_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.addEdit.deleteMonitorLabel', + { + defaultMessage: 'Delete monitor', + } +); + const UPDATE_MONITOR_LABEL = i18n.translate( 'xpack.synthetics.monitorManagement.updateMonitorLabel', { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_status.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_status.tsx index ac2ae19700e415..8b60edce8b93ff 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_status.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_status.tsx @@ -5,15 +5,12 @@ * 2.0. */ import React from 'react'; -import { EuiBadge, EuiDescriptionList, EuiLoadingSpinner } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useTheme } from '@kbn/observability-plugin/public'; +import { MonitorStatus } from '../common/components/monitor_status'; import { useSelectedMonitor } from './hooks/use_selected_monitor'; import { useMonitorLatestPing } from './hooks/use_monitor_latest_ping'; -export const MonitorDetailsStatus: React.FC = () => { - const theme = useTheme(); +export const MonitorDetailsStatus = () => { const { latestPing, loading: pingsLoading } = useMonitorLatestPing(); const { monitor } = useSelectedMonitor(); @@ -22,41 +19,12 @@ export const MonitorDetailsStatus: React.FC = () => { return null; } - const isBrowserType = monitor.type === 'browser'; - - const badge = pingsLoading ? ( - - ) : !latestPing ? ( - {PENDING_LABEL} - ) : latestPing.monitor.status === 'up' ? ( - {isBrowserType ? SUCCESS_LABEL : UP_LABEL} - ) : ( - {isBrowserType ? FAILED_LABEL : DOWN_LABEL} + return ( + ); - - return ; }; - -const STATUS_LABEL = i18n.translate('xpack.synthetics.monitorStatus.statusLabel', { - defaultMessage: 'Status', -}); - -const FAILED_LABEL = i18n.translate('xpack.synthetics.monitorStatus.failedLabel', { - defaultMessage: 'Failed', -}); - -const PENDING_LABEL = i18n.translate('xpack.synthetics.monitorStatus.pendingLabel', { - defaultMessage: 'Pending', -}); - -const SUCCESS_LABEL = i18n.translate('xpack.synthetics.monitorStatus.succeededLabel', { - defaultMessage: 'Succeeded', -}); - -const UP_LABEL = i18n.translate('xpack.synthetics.monitorStatus.upLabel', { - defaultMessage: 'Up', -}); - -const DOWN_LABEL = i18n.translate('xpack.synthetics.monitorStatus.downLabel', { - defaultMessage: 'Down', -}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx index aeb5010f3496e9..274d0a9d283171 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx @@ -5,19 +5,10 @@ * 2.0. */ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useState } from 'react'; import { EuiThemeComputed } from '@elastic/eui/src/services/theme/types'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import { FETCH_STATUS, useFetcher } from '@kbn/observability-plugin/public'; -import { - EuiContextMenuPanel, - EuiContextMenuItem, - EuiPopover, - EuiButtonEmpty, - EuiConfirmModal, -} from '@elastic/eui'; -import { kibanaService } from '../../../../../../utils/kibana_service'; -import { fetchDeleteMonitor } from '../../../../state'; +import { EuiContextMenuPanel, EuiContextMenuItem, EuiPopover, EuiButtonEmpty } from '@elastic/eui'; +import { DeleteMonitor } from './delete_monitor'; import { SyntheticsSettingsContext } from '../../../../contexts/synthetics_settings_context'; import * as labels from './labels'; @@ -27,54 +18,23 @@ interface Props { id: string; name: string; canEditSynthetics: boolean; + isProjectMonitor?: boolean; reloadPage: () => void; } -export const Actions = ({ euiTheme, id, name, reloadPage, canEditSynthetics }: Props) => { +export const Actions = ({ + euiTheme, + id, + name, + reloadPage, + canEditSynthetics, + isProjectMonitor, +}: Props) => { const { basePath } = useContext(SyntheticsSettingsContext); const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const [isDeleting, setIsDeleting] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); - const { status: monitorDeleteStatus } = useFetcher(() => { - if (isDeleting) { - return fetchDeleteMonitor({ id }); - } - }, [id, isDeleting]); - // TODO: Move deletion logic to redux state - useEffect(() => { - if (!isDeleting) { - return; - } - if ( - monitorDeleteStatus === FETCH_STATUS.SUCCESS || - monitorDeleteStatus === FETCH_STATUS.FAILURE - ) { - setIsDeleting(false); - setIsDeleteModalVisible(false); - } - if (monitorDeleteStatus === FETCH_STATUS.FAILURE) { - kibanaService.toasts.addDanger( - { - title: toMountPoint( -

{labels.MONITOR_DELETE_FAILURE_LABEL}

- ), - }, - { toastLifeTimeMs: 3000 } - ); - } else if (monitorDeleteStatus === FETCH_STATUS.SUCCESS) { - reloadPage(); - kibanaService.toasts.addSuccess( - { - title: toMountPoint( -

{labels.MONITOR_DELETE_SUCCESS_LABEL}

- ), - }, - { toastLifeTimeMs: 3000 } - ); - } - }, [setIsDeleting, isDeleting, reloadPage, monitorDeleteStatus]); const openPopover = () => { setIsPopoverOpen(true); @@ -89,10 +49,6 @@ export const Actions = ({ euiTheme, id, name, reloadPage, canEditSynthetics }: P closePopover(); }; - const handleConfirmDelete = () => { - setIsDeleting(true); - }; - const menuButton = ( - {isDeleteModalVisible ? ( - setIsDeleteModalVisible(false)} - onConfirm={handleConfirmDelete} - cancelButtonText={labels.NO_LABEL} - confirmButtonText={labels.YES_LABEL} - buttonColor="danger" - defaultFocusedButton="confirm" - isLoading={isDeleting} - > -

{labels.DELETE_DESCRIPTION_LABEL}

-
- ) : null} + {isDeleteModalVisible && ( + + )} ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx index 14aaf8c15d358c..e847fdd5c24002 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx @@ -18,6 +18,7 @@ import { EncryptedSyntheticsSavedMonitor, Ping, ServiceLocations, + SourceType, SyntheticsMonitorSchedule, } from '../../../../../../../common/runtime_types'; @@ -155,6 +156,7 @@ export function getMonitorListColumns({ name={fields[ConfigKey.NAME]} canEditSynthetics={canEditSynthetics} reloadPage={reloadPage} + isProjectMonitor={fields[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT} /> ), }, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx new file mode 100644 index 00000000000000..4dd4e5a0b19a1e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor.tsx @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; +import { EuiCallOut, EuiConfirmModal, EuiLink, EuiSpacer } from '@elastic/eui'; +import { FETCH_STATUS, useFetcher } from '@kbn/observability-plugin/public'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { i18n } from '@kbn/i18n'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { fetchDeleteMonitor } from '../../../../state'; +import { kibanaService } from '../../../../../../utils/kibana_service'; +import * as labels from './labels'; + +export const DeleteMonitor = ({ + id, + name, + reloadPage, + isProjectMonitor, + setIsDeleteModalVisible, +}: { + id: string; + name: string; + reloadPage: () => void; + isProjectMonitor?: boolean; + setIsDeleteModalVisible: React.Dispatch>; +}) => { + const [isDeleting, setIsDeleting] = useState(false); + + const handleConfirmDelete = () => { + setIsDeleting(true); + }; + + const { status: monitorDeleteStatus } = useFetcher(() => { + if (isDeleting) { + return fetchDeleteMonitor({ id }); + } + }, [id, isDeleting]); + + useEffect(() => { + if (!isDeleting) { + return; + } + if (monitorDeleteStatus === FETCH_STATUS.FAILURE) { + kibanaService.toasts.addDanger( + { + title: toMountPoint( +

{labels.MONITOR_DELETE_FAILURE_LABEL}

+ ), + }, + { toastLifeTimeMs: 3000 } + ); + } else if (monitorDeleteStatus === FETCH_STATUS.SUCCESS) { + reloadPage(); + kibanaService.toasts.addSuccess( + { + title: toMountPoint( +

+ {i18n.translate( + 'xpack.synthetics.monitorManagement.monitorDeleteSuccessMessage.name', + { + defaultMessage: 'Monitor {name} deleted successfully.', + values: { name }, + } + )} +

+ ), + }, + { toastLifeTimeMs: 3000 } + ); + } + if ( + monitorDeleteStatus === FETCH_STATUS.SUCCESS || + monitorDeleteStatus === FETCH_STATUS.FAILURE + ) { + setIsDeleting(false); + setIsDeleteModalVisible(false); + } + }, [setIsDeleting, isDeleting, reloadPage, monitorDeleteStatus, setIsDeleteModalVisible, name]); + + return ( + setIsDeleteModalVisible(false)} + onConfirm={handleConfirmDelete} + cancelButtonText={labels.NO_LABEL} + confirmButtonText={labels.YES_LABEL} + buttonColor="danger" + defaultFocusedButton="confirm" + isLoading={isDeleting} + > + {isProjectMonitor && ( + <> + +

+ +

+
+ + + )} +
+ ); +}; + +export const PROJECT_MONITOR_TITLE = i18n.translate( + 'xpack.synthetics.monitorManagement.monitorList.disclaimer.title', + { + defaultMessage: "Deleting this monitor will not remove it from Project's source", + } +); + +export const ProjectMonitorDisclaimer = () => { + return ( + + {i18n.translate('xpack.synthetics.monitorManagement.projectDelete.docsLink', { + defaultMessage: 'read our docs', + })} + + ), + }} + /> + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx index 34b76441bd52b1..0c9e0c418118c3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.test.tsx @@ -134,7 +134,7 @@ describe('Monitor Detail Flyout', () => { expect(getByText('Every 1 minute')); expect(getByText('test-id')); - expect(getByText('Up')); + expect(getByText('Pending')); expect( getByRole('heading', { level: 2, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx index 2904e4a9f858ee..6e0e15e6cdda2c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx @@ -58,6 +58,7 @@ import { } from '../types'; import { useMonitorDetailLocator } from '../../hooks/use_monitor_detail_locator'; import { fetchSyntheticsMonitor } from '../../../../state/overview/api'; +import { MonitorStatus } from '../../../common/components/monitor_status'; interface Props { id: string; @@ -174,7 +175,8 @@ function LocationSelect({ setCurrentLocation: (location: string) => void; }) { const [isOpen, setIsOpen] = useState(false); - const isDown = !!locations.find((l) => l.observer?.geo?.name === currentLocation)?.summary?.down; + const status = locations.find((l) => l.observer?.geo?.name === currentLocation)?.monitor?.status; + return ( @@ -227,14 +229,7 @@ function LocationSelect({
- - {STATUS_TITLE_TEXT} - - - {isDown ? MONITOR_STATUS_DOWN_LABEL : MONITOR_STATUS_UP_LABEL} - - - + @@ -514,10 +509,6 @@ const LAST_RUN_HEADER_TEXT = i18n.translate('xpack.synthetics.monitorList.lastRu defaultMessage: 'Last run', }); -const STATUS_TITLE_TEXT = i18n.translate('xpack.synthetics.monitorList.statusColumnName', { - defaultMessage: 'Status', -}); - const LOCATION_TITLE_TEXT = i18n.translate('xpack.synthetics.monitorList.locationColumnName', { defaultMessage: 'Location', }); @@ -582,22 +573,6 @@ const LOCATION_SELECT_POPOVER_LINK_LABEL = i18n.translate( } ); -const MONITOR_STATUS_UP_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.flyout.monitorStatus.up', - { - defaultMessage: 'Up', - description: '"Up" in the sense that a process is running and available.', - } -); - -const MONITOR_STATUS_DOWN_LABEL = i18n.translate( - 'xpack.synthetics.monitorList.flyout.monitorStatus.down', - { - defaultMessage: 'Down', - description: '"Down" in the sense that a process is not running or available.', - } -); - function translateUnitMessage(unitMsg: string) { return i18n.translate('xpack.synthetics.monitorList.flyout.unitStr', { defaultMessage: 'Every {unitMsg}', diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.test.tsx index 930d89f97af2af..4c3d80a4d68cd2 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.test.tsx @@ -46,7 +46,7 @@ describe('', () => { ); }); - it('disables deleting for project monitors', () => { + it('allows deleting for project monitors', () => { render( ', () => { /> ); - expect(screen.getByLabelText('Delete monitor')).toBeDisabled(); + expect(screen.getByLabelText('Delete monitor')).not.toBeDisabled(); }); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.tsx index a21dedce9043a9..78e46f6a0fea83 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/actions.tsx @@ -74,23 +74,13 @@ export const Actions = ({ - - - + {errorSummary && ( diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.test.tsx index e708be5c8921f8..5915aa27e6df5f 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.test.tsx @@ -19,6 +19,7 @@ import { MonitorManagementListResult, SourceType, } from '../../../../../common/runtime_types'; +import userEvent from '@testing-library/user-event'; describe('', () => { const onUpdate = jest.fn(); @@ -61,7 +62,7 @@ describe('', () => { /> ); - fireEvent.click(screen.getByLabelText('Delete monitor')); + userEvent.click(screen.getByTestId('monitorManagementDeleteMonitor')); expect(onUpdate).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.tsx index 7e3735834644e2..68925a72f78d4a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_list/delete_monitor.tsx @@ -7,10 +7,20 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; -import { EuiButtonIcon, EuiConfirmModal, EuiLoadingSpinner } from '@elastic/eui'; +import { + EuiButtonIcon, + EuiCallOut, + EuiConfirmModal, + EuiLoadingSpinner, + EuiSpacer, +} from '@elastic/eui'; import { FETCH_STATUS, useFetcher } from '@kbn/observability-plugin/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { + ProjectMonitorDisclaimer, + PROJECT_MONITOR_TITLE, +} from '../../../../apps/synthetics/components/monitors_page/management/monitor_list_table/delete_monitor'; import { deleteMonitor } from '../../../state/api'; import { kibanaService } from '../../../state/kibana_service'; @@ -19,10 +29,12 @@ export const DeleteMonitor = ({ name, onUpdate, isDisabled, + isProjectMonitor, }: { configId: string; name: string; isDisabled?: boolean; + isProjectMonitor?: boolean; onUpdate: () => void; }) => { const [isDeleting, setIsDeleting] = useState(false); @@ -62,17 +74,28 @@ export const DeleteMonitor = ({ kibanaService.toasts.addSuccess( { title: toMountPoint( -

{MONITOR_DELETE_SUCCESS_LABEL}

+

+ {i18n.translate( + 'xpack.synthetics.monitorManagement.monitorDeleteSuccessMessage.name', + { + defaultMessage: 'Monitor {name} deleted successfully.', + values: { name }, + } + )} +

), }, { toastLifeTimeMs: 3000 } ); } - }, [setIsDeleting, onUpdate, status]); + }, [setIsDeleting, onUpdate, status, name]); const destroyModal = ( setIsDeleteModalVisible(false)} onConfirm={onConfirmDelete} cancelButtonText={NO_LABEL} @@ -80,7 +103,16 @@ export const DeleteMonitor = ({ buttonColor="danger" defaultFocusedButton="confirm" > -

{DELETE_DESCRIPTION_LABEL}

+ {isProjectMonitor && ( + <> + +

+ +

+
+ + + )}
); @@ -102,14 +134,6 @@ export const DeleteMonitor = ({ ); }; -const DELETE_DESCRIPTION_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.confirmDescriptionLabel', - { - defaultMessage: - 'This action will delete the monitor but keep any data collected. This action cannot be undone.', - } -); - const YES_LABEL = i18n.translate('xpack.synthetics.monitorManagement.yesLabel', { defaultMessage: 'Delete', }); @@ -125,13 +149,6 @@ const DELETE_MONITOR_LABEL = i18n.translate( } ); -const MONITOR_DELETE_SUCCESS_LABEL = i18n.translate( - 'xpack.synthetics.monitorManagement.monitorDeleteSuccessMessage', - { - defaultMessage: 'Monitor deleted successfully.', - } -); - // TODO: Discuss error states with product const MONITOR_DELETE_FAILURE_LABEL = i18n.translate( 'xpack.synthetics.monitorManagement.monitorDeleteFailureMessage', diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alerts_containers/use_snap_shot.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alerts_containers/use_snap_shot.ts index 7fd0f0b3a410bc..df402944cb4625 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alerts_containers/use_snap_shot.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/alerts/alerts_containers/use_snap_shot.ts @@ -6,7 +6,7 @@ */ import { useFetcher } from '@kbn/observability-plugin/public'; -import { useUptimeDataView, generateUpdatedKueryString } from '../../../../hooks'; +import { useGenerateUpdatedKueryString } from '../../../../hooks'; import { fetchSnapshotCount } from '../../../../state/api'; export const useSnapShotCount = ({ query, filters }: { query: string; filters: [] | string }) => { @@ -15,9 +15,7 @@ export const useSnapShotCount = ({ query, filters }: { query: string; filters: [ ? '' : JSON.stringify(Array.from(Object.entries(filters))); - const dataView = useUptimeDataView(); - - const [esKuery, error] = generateUpdatedKueryString(dataView, query, parsedFilters); + const [esKuery, error] = useGenerateUpdatedKueryString(query, parsedFilters, undefined, true); const { data, loading } = useFetcher( () => diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx index 16ab92a6862c24..5f10d29d4814ff 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/filter_group/filter_group.tsx @@ -10,17 +10,21 @@ import { EuiFilterGroup } from '@elastic/eui'; import styled from 'styled-components'; import { capitalize } from 'lodash'; import { FieldValueSuggestions, useInspectorContext } from '@kbn/observability-plugin/public'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { useFilterUpdate } from '../../../hooks/use_filter_update'; import { useSelectedFilters } from '../../../hooks/use_selected_filters'; import { SelectedFilters } from './selected_filters'; import { useUptimeDataView } from '../../../contexts/uptime_data_view_context'; import { useGetUrlParams } from '../../../hooks'; import { EXCLUDE_RUN_ONCE_FILTER } from '../../../../../common/constants/client_defaults'; +import { useUptimeRefreshContext } from '../../../contexts/uptime_refresh_context'; const Container = styled(EuiFilterGroup)` margin-bottom: 10px; `; +export const TAG_KEY_FOR_AND_CONDITION = 'useANDForTagsFilter'; + export const FilterGroup = () => { const [updatedFieldValues, setUpdatedFieldValues] = useState<{ fieldName: string; @@ -34,6 +38,8 @@ export const FilterGroup = () => { updatedFieldValues.notValues ); + const { refreshApp } = useUptimeRefreshContext(); + const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); const { inspectorAdapters } = useInspectorContext(); @@ -42,6 +48,8 @@ export const FilterGroup = () => { const dataView = useUptimeDataView(); + const [useLogicalAND, setLogicalANDForTag] = useLocalStorage(TAG_KEY_FOR_AND_CONDITION, false); + const onFilterFieldChange = useCallback( (fieldName: string, values: string[], notValues: string[]) => { setUpdatedFieldValues({ fieldName, values, notValues }); @@ -62,9 +70,13 @@ export const FilterGroup = () => { label={label} selectedValue={selectedItems} excludedValue={excludedItems} - onChange={(values, notValues) => - onFilterFieldChange(field, values ?? [], notValues ?? []) - } + onChange={(values, notValues, isLogicalAND) => { + onFilterFieldChange(field, values ?? [], notValues ?? []); + if (isLogicalAND !== undefined) { + setLogicalANDForTag(isLogicalAND); + setTimeout(() => refreshApp(), 0); + } + }} asCombobox={false} asFilterButton={true} forceOpen={false} @@ -82,6 +94,8 @@ export const FilterGroup = () => { adapter: inspectorAdapters.requests, title: 'get' + capitalize(label) + 'FilterValues', }} + showLogicalConditionSwitch={field === 'tags'} + useLogicalAND={field === 'tags' ? useLogicalAND : undefined} /> ))} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/use_query_bar.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/use_query_bar.test.tsx index 56a6fd7e6dc301..67fdf866186412 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/use_query_bar.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/use_query_bar.test.tsx @@ -12,7 +12,7 @@ import { MockRouter, MockKibanaProvider } from '../../../lib/helper/rtl_helpers' import { SyntaxType, useQueryBar, DEBOUNCE_INTERVAL } from './use_query_bar'; import { MountWithReduxProvider } from '../../../lib'; import * as URL from '../../../hooks/use_url_params'; -import * as ES_FILTERS from '../../../hooks/update_kuery_string'; +import * as ES_FILTERS from '../../../hooks/use_update_kuery_string'; import { UptimeUrlParams } from '../../../lib/helper/url_params'; const SAMPLE_ES_FILTERS = `{"bool":{"should":[{"match_phrase":{"monitor.id":"NodeServer"}}],"minimum_should_match":1}}`; @@ -49,7 +49,7 @@ describe.skip('useQueryBar', () => { ); useUrlParamsSpy = jest.spyOn(URL, 'useUrlParams'); useGetUrlParamsSpy = jest.spyOn(URL, 'useGetUrlParams'); - useUpdateKueryStringSpy = jest.spyOn(ES_FILTERS, 'generateUpdatedKueryString'); + useUpdateKueryStringSpy = jest.spyOn(ES_FILTERS, 'useGenerateUpdatedKueryString'); updateUrlParamsMock = jest.fn(); useUrlParamsSpy.mockImplementation(() => [jest.fn(), updateUrlParamsMock]); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/use_query_bar.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/use_query_bar.ts index 23dd326b7e18b9..63a2377c5a45f1 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/use_query_bar.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/query_bar/use_query_bar.ts @@ -10,12 +10,7 @@ import useDebounce from 'react-use/lib/useDebounce'; import { useDispatch } from 'react-redux'; import type { Query } from '@kbn/es-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { - useGetUrlParams, - useUptimeDataView, - generateUpdatedKueryString, - useUrlParams, -} from '../../../hooks'; +import { useGetUrlParams, useGenerateUpdatedKueryString, useUrlParams } from '../../../hooks'; import { setEsKueryString } from '../../../state/actions'; import { UptimePluginServices } from '../../../../plugin'; @@ -70,12 +65,9 @@ export const useQueryBar = (): UseQueryBarUtils => { } ); - const dataView = useUptimeDataView(); - const [, updateUrlParams] = useUrlParams(); - const [esFilters, error] = generateUpdatedKueryString( - dataView, + const [esFilters, error] = useGenerateUpdatedKueryString( query.language === SyntaxType.kuery ? (query.query as string) : undefined, paramFilters, excludedFilters diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/index.ts index 6d2a826caa4b48..bd9a6390e7f2e3 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/index.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/index.ts @@ -6,7 +6,7 @@ */ export * from './use_composite_image'; -export * from './update_kuery_string'; +export * from './use_update_kuery_string'; export * from './use_monitor'; export * from './use_search_text'; export * from './use_cert_status'; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/update_kuery_string.ts b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_update_kuery_string.ts similarity index 60% rename from x-pack/plugins/synthetics/public/legacy_uptime/hooks/update_kuery_string.ts rename to x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_update_kuery_string.ts index 007dfd12427f34..823c57edfd051b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/hooks/update_kuery_string.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/hooks/use_update_kuery_string.ts @@ -6,10 +6,17 @@ */ import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; -import { DataView } from '@kbn/data-views-plugin/public'; +import { useEffect, useState } from 'react'; +import { useUptimeRefreshContext } from '../contexts/uptime_refresh_context'; +import { useUptimeDataView } from '../contexts/uptime_data_view_context'; +import { TAG_KEY_FOR_AND_CONDITION } from '../components/overview/filter_group/filter_group'; import { combineFiltersAndUserSearch, stringifyKueries } from '../../../common/lib'; -const getKueryString = (urlFilters: string, excludedFilters?: string): string => { +const getKueryString = ( + urlFilters: string, + excludedFilters?: string, + logicalANDForTag?: boolean +): string => { let kueryString = ''; let excludeKueryString = ''; // We are using try/catch here because this is user entered value @@ -18,7 +25,7 @@ const getKueryString = (urlFilters: string, excludedFilters?: string): string => try { if (urlFilters !== '') { const filterMap = new Map>(JSON.parse(urlFilters)); - kueryString = stringifyKueries(filterMap); + kueryString = stringifyKueries(filterMap, logicalANDForTag); } } catch { kueryString = ''; @@ -27,7 +34,7 @@ const getKueryString = (urlFilters: string, excludedFilters?: string): string => try { if (excludedFilters) { const filterMap = new Map>(JSON.parse(excludedFilters)); - excludeKueryString = stringifyKueries(filterMap); + excludeKueryString = stringifyKueries(filterMap, logicalANDForTag); if (kueryString) { return `${kueryString} and NOT (${excludeKueryString})`; } @@ -41,13 +48,28 @@ const getKueryString = (urlFilters: string, excludedFilters?: string): string => return `NOT (${excludeKueryString})`; }; -export const generateUpdatedKueryString = ( - dataView: DataView | null, +export const useGenerateUpdatedKueryString = ( filterQueryString = '', urlFilters: string, - excludedFilters?: string + excludedFilters?: string, + disableANDFiltering?: boolean ): [string?, Error?] => { - const kueryString = getKueryString(urlFilters, excludedFilters); + const dataView = useUptimeDataView(); + + const { lastRefresh } = useUptimeRefreshContext(); + + const [kueryString, setKueryString] = useState(''); + + useEffect(() => { + if (disableANDFiltering) { + setKueryString(getKueryString(urlFilters, excludedFilters)); + } else { + // need a string comparison for local storage + const useLogicalAND = localStorage.getItem(TAG_KEY_FOR_AND_CONDITION) === 'true'; + + setKueryString(getKueryString(urlFilters, excludedFilters, useLogicalAND)); + } + }, [excludedFilters, urlFilters, lastRefresh, disableANDFiltering]); const combinedFilterString = combineFiltersAndUserSearch(filterQueryString, kueryString); diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index e0b449a6afa8b5..409f29bfc437be 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -4071,6 +4071,72 @@ }, "indices": { "properties": { + "metric": { + "properties": { + "shards": { + "properties": { + "total": { + "type": "long" + } + } + }, + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "traces": { + "properties": { + "shards": { + "properties": { + "total": { + "type": "long" + } + } + }, + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, "shards": { "properties": { "total": { @@ -4122,6 +4188,12 @@ "service_id": { "type": "keyword" }, + "num_service_nodes": { + "type": "long" + }, + "num_transaction_types": { + "type": "long" + }, "timed_out": { "type": "boolean" }, @@ -13015,7 +13087,9 @@ "properties": { "unique_endpoint_count": { "type": "long", - "_meta": { "description": "Number of active unique endpoints in last 24 hours" } + "_meta": { + "description": "Number of active unique endpoints in last 24 hours" + } } } } diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/cases.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/cases.cy.ts new file mode 100644 index 00000000000000..52cdf25da68c21 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/cases.cy.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 { + CASE_ACTION_WRAPPER, + CASE_COMMENT_EXTERNAL_REFERENCE, + CASE_ELLIPSE_BUTTON, + CASE_ELLIPSE_DELETE_CASE_CONFIRMATION_BUTTON, + CASE_ELLIPSE_DELETE_CASE_OPTION, + CREAT_CASE_BUTTON, + FLYOUT_ADD_TO_EXISTING_CASE_ITEM, + FLYOUT_ADD_TO_NEW_CASE_ITEM, + FLYOUT_TAKE_ACTION_BUTTON, + INDICATORS_TABLE_ADD_TO_EXISTING_CASE_BUTTON_ICON, + INDICATORS_TABLE_ADD_TO_NEW_CASE_BUTTON_ICON, + INDICATORS_TABLE_MORE_ACTION_BUTTON_ICON, + NEW_CASE_CREATE_BUTTON, + NEW_CASE_DESCRIPTION_INPUT, + NEW_CASE_NAME_INPUT, + SELECT_EXISTING_CASE, + TOGGLE_FLYOUT_BUTTON, + VIEW_CASE_TOASTER_LINK, +} from '../screens/indicators'; +import { login } from '../tasks/login'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { selectRange } from '../tasks/select_range'; + +before(() => { + login(); +}); + +const THREAT_INTELLIGENCE = '/app/security/threat_intelligence/indicators'; +const CASES = 'app/security/cases'; + +const createNewCaseFromCases = () => { + cy.visit(CASES); + cy.get(CREAT_CASE_BUTTON).click(); + cy.get(NEW_CASE_NAME_INPUT).click().type('case'); + cy.get(NEW_CASE_DESCRIPTION_INPUT).click().type('case description'); + cy.get(NEW_CASE_CREATE_BUTTON).click(); +}; + +const createNewCaseFromTI = () => { + cy.get(NEW_CASE_NAME_INPUT).type('case'); + cy.get(NEW_CASE_DESCRIPTION_INPUT).type('case description'); + cy.get(NEW_CASE_CREATE_BUTTON).click(); +}; + +const selectExistingCase = () => { + cy.wait(1000); // TODO find a better way to wait for the table to render + cy.get(SELECT_EXISTING_CASE).should('exist').contains('Select').click(); +}; + +const navigateToNewCaseAndCheckAddedComment = () => { + cy.get(VIEW_CASE_TOASTER_LINK).click(); + cy.get(CASE_COMMENT_EXTERNAL_REFERENCE) + .should('exist') + .and('contain.text', 'added an indicator of compromise') + .and('contain.text', 'Indicator name') + .and('contain.text', 'Indicator type') + .and('contain.text', 'Feed name'); +}; + +const deleteCase = () => { + cy.get(CASE_ACTION_WRAPPER).find(CASE_ELLIPSE_BUTTON).click(); + cy.get(CASE_ELLIPSE_DELETE_CASE_OPTION).click(); + cy.get(CASE_ELLIPSE_DELETE_CASE_CONFIRMATION_BUTTON).click(); +}; + +describe('Cases with invalid indicators', () => { + before(() => { + esArchiverLoad('threat_intelligence/invalid_indicators_data'); + + cy.visit(THREAT_INTELLIGENCE); + selectRange(); + }); + after(() => { + esArchiverUnload('threat_intelligence/invalid_indicators_data'); + }); + + it('should disable the indicators table context menu items if invalid indicator', () => { + const documentsNumber = 22; + cy.get(INDICATORS_TABLE_MORE_ACTION_BUTTON_ICON) + .eq(documentsNumber - 1) + .click(); + cy.get(INDICATORS_TABLE_ADD_TO_EXISTING_CASE_BUTTON_ICON).should('be.disabled'); + cy.get(INDICATORS_TABLE_ADD_TO_NEW_CASE_BUTTON_ICON).should('be.disabled'); + }); + + it('should disable the flyout context menu items if invalid indicator', () => { + const documentsNumber = 22; + cy.get(TOGGLE_FLYOUT_BUTTON) + .eq(documentsNumber - 1) + .click({ force: true }); + cy.get(FLYOUT_TAKE_ACTION_BUTTON).first().click(); + cy.get(FLYOUT_ADD_TO_EXISTING_CASE_ITEM).should('be.disabled'); + cy.get(FLYOUT_ADD_TO_NEW_CASE_ITEM).should('be.disabled'); + }); +}); + +describe('Cases interactions', () => { + before(() => { + esArchiverLoad('threat_intelligence/indicators_data'); + }); + after(() => { + esArchiverUnload('threat_intelligence/indicators_data'); + }); + + it('should add to existing case when clicking on the button in the indicators table', () => { + createNewCaseFromCases(); + + cy.visit(THREAT_INTELLIGENCE); + selectRange(); + + cy.get(INDICATORS_TABLE_MORE_ACTION_BUTTON_ICON).first().click(); + cy.get(INDICATORS_TABLE_ADD_TO_EXISTING_CASE_BUTTON_ICON).first().click(); + + selectExistingCase(); + navigateToNewCaseAndCheckAddedComment(); + deleteCase(); + }); + + it('should add to new case when clicking on the button in the indicators table', () => { + cy.visit(THREAT_INTELLIGENCE); + selectRange(); + + cy.get(INDICATORS_TABLE_MORE_ACTION_BUTTON_ICON).first().click(); + cy.get(INDICATORS_TABLE_ADD_TO_NEW_CASE_BUTTON_ICON).first().click(); + createNewCaseFromTI(); + + navigateToNewCaseAndCheckAddedComment(); + deleteCase(); + }); + + it('should add to existing case when clicking on the button in the indicators flyout', () => { + createNewCaseFromCases(); + + cy.visit(THREAT_INTELLIGENCE); + selectRange(); + + cy.get(TOGGLE_FLYOUT_BUTTON).first().click({ force: true }); + cy.get(FLYOUT_TAKE_ACTION_BUTTON).first().click(); + cy.get(FLYOUT_ADD_TO_EXISTING_CASE_ITEM).first().click(); + + selectExistingCase(); + navigateToNewCaseAndCheckAddedComment(); + deleteCase(); + }); + + it('should add to new case when clicking on the button in the indicators flyout', () => { + cy.visit(THREAT_INTELLIGENCE); + selectRange(); + + cy.get(TOGGLE_FLYOUT_BUTTON).first().click({ force: true }); + cy.get(FLYOUT_TAKE_ACTION_BUTTON).first().click(); + cy.get(FLYOUT_ADD_TO_NEW_CASE_ITEM).first().click(); + createNewCaseFromTI(); + + navigateToNewCaseAndCheckAddedComment(); + deleteCase(); + }); +}); 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 b69f57a7b3f47d..23aac220fc5cde 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts @@ -11,7 +11,6 @@ import { FLYOUT_CLOSE_BUTTON, FLYOUT_OVERVIEW_TAB_BLOCKS_TIMELINE_BUTTON, FLYOUT_OVERVIEW_TAB_TABLE_ROW_TIMELINE_BUTTON, - INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON, INDICATOR_TYPE_CELL, INDICATORS_TABLE_CELL_TIMELINE_BUTTON, INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON, @@ -20,6 +19,8 @@ import { UNTITLED_TIMELINE_BUTTON, FLYOUT_TABLE_MORE_ACTIONS_BUTTON, FLYOUT_BLOCK_MORE_ACTIONS_BUTTON, + FLYOUT_TAKE_ACTION_BUTTON, + FLYOUT_INVESTIGATE_IN_TIMELINE_ITEM, } from '../screens/indicators'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { login } from '../tasks/login'; @@ -86,7 +87,8 @@ describe('Indicators', () => { it('should investigate in timeline when clicking in an indicator flyout', () => { cy.get(TOGGLE_FLYOUT_BUTTON).first().click({ force: true }); - cy.get(INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON).should('exist').first().click(); + cy.get(FLYOUT_TAKE_ACTION_BUTTON).first().click(); + cy.get(FLYOUT_INVESTIGATE_IN_TIMELINE_ITEM).should('exist').first().click(); cy.get(UNTITLED_TIMELINE_BUTTON).should('exist').first().click(); cy.get(TIMELINE_DRAGGABLE_ITEM).should('exist'); }); diff --git a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts index 4b405b35d26f63..6a8f723d22ece8 100644 --- a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts +++ b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts @@ -54,8 +54,14 @@ export const INDICATORS_TABLE_CELL_FILTER_OUT_BUTTON = export const INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON = '[data-test-subj="tiIndicatorTableInvestigateInTimelineButtonIcon"]'; -export const INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON = - '[data-test-subj="tiIndicatorFlyoutInvestigateInTimelineButton"]'; +export const INDICATORS_TABLE_MORE_ACTION_BUTTON_ICON = + '[data-test-subj="tiIndicatorTableMoreActionsButton"]'; + +export const INDICATORS_TABLE_ADD_TO_NEW_CASE_BUTTON_ICON = + '[data-test-subj="tiIndicatorTableAddToNewCaseContextMenu"]'; + +export const INDICATORS_TABLE_ADD_TO_EXISTING_CASE_BUTTON_ICON = + '[data-test-subj="tiIndicatorTableAddToExistingCaseContextMenu"]'; /* Flyout */ @@ -107,6 +113,17 @@ export const FLYOUT_TABLE_TAB_ROW_FILTER_IN_BUTTON = export const FLYOUT_TABLE_TAB_ROW_FILTER_OUT_BUTTON = '[data-test-subj="tiFlyoutTableFilterOutButton"]'; +export const FLYOUT_TAKE_ACTION_BUTTON = '[data-test-subj="tiIndicatorFlyoutTakeActionButton"]'; + +export const FLYOUT_ADD_TO_EXISTING_CASE_ITEM = + '[data-test-subj="tiIndicatorFlyoutAddToExistingCaseContextMenu"]'; + +export const FLYOUT_ADD_TO_NEW_CASE_ITEM = + '[data-test-subj="tiIndicatorFlyoutAddToNewCaseContextMenu"]'; + +export const FLYOUT_INVESTIGATE_IN_TIMELINE_ITEM = + '[data-test-subj="tiIndicatorFlyoutInvestigateInTimelineContextMenu"]'; + /* Field selector */ export const FIELD_SELECTOR = '[data-test-subj="tiIndicatorFieldSelectorDropdown"]'; @@ -134,6 +151,32 @@ export const BARCHART_FILTER_IN_BUTTON = '[data-test-subj="tiBarchartFilterInBut export const BARCHART_FILTER_OUT_BUTTON = '[data-test-subj="tiBarchartFilterOutButton"]'; +/* Cases */ + +export const CREAT_CASE_BUTTON = '[data-test-subj="createNewCaseBtn"]'; + +export const SELECT_EXISTING_CASE = '[class="eui-textTruncate"]'; + +export const VIEW_CASE_TOASTER_LINK = '[data-test-subj="toaster-content-case-view-link"]'; + +export const CASE_COMMENT_EXTERNAL_REFERENCE = + '[data-test-subj="comment-externalReference-indicator"]'; + +export const CASE_ACTION_WRAPPER = '[data-test-subj="case-action-bar-wrapper"]'; + +export const CASE_ELLIPSE_BUTTON = '[data-test-subj="property-actions-ellipses"]'; + +export const CASE_ELLIPSE_DELETE_CASE_OPTION = '[data-test-subj="property-actions-trash"]'; + +export const CASE_ELLIPSE_DELETE_CASE_CONFIRMATION_BUTTON = + '[data-test-subj="confirmModalConfirmButton"]'; + +export const NEW_CASE_NAME_INPUT = '[data-test-subj="input"][aria-describedby="caseTitle"]'; + +export const NEW_CASE_DESCRIPTION_INPUT = '[data-test-subj="euiMarkdownEditorTextArea"]'; + +export const NEW_CASE_CREATE_BUTTON = '[data-test-subj="create-case-submit"]'; + /* Miscellaneous */ export const UNTITLED_TIMELINE_BUTTON = '[data-test-subj="flyoutOverlay"]'; diff --git a/x-pack/plugins/threat_intelligence/kibana.json b/x-pack/plugins/threat_intelligence/kibana.json index 16fcf4eeb5c4c8..28f0072d248141 100644 --- a/x-pack/plugins/threat_intelligence/kibana.json +++ b/x-pack/plugins/threat_intelligence/kibana.json @@ -10,6 +10,7 @@ }, "description": "Elastic threat intelligence helps you see if you are open to or have been subject to current or historical known threats", "requiredPlugins": [ + "cases", "data", "dataViews", "kibanaUtils", diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx index f1f5e3c215eb2d..cc80029fc960d2 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx @@ -65,6 +65,12 @@ const defaultServices = { set: () => {}, get: () => {}, }, + cases: { + hooks: { + getUseCasesAddToNewCaseFlyout: () => {}, + getUseCasesAddToExistingCaseModal: () => {}, + }, + }, } as unknown as CoreStart; /** diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx index 7163b84f81a6ee..8d77e98577ee72 100644 --- a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx +++ b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx @@ -19,6 +19,7 @@ import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; +import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; import { KibanaContext } from '../../hooks'; import { SecuritySolutionPluginContext } from '../../types'; import { getSecuritySolutionContextMock } from './mock_security_context'; @@ -110,9 +111,12 @@ const coreServiceMock = { const mockSecurityContext: SecuritySolutionPluginContext = getSecuritySolutionContextMock(); +const casesServiceMock = casesPluginMock.createStartContract(); + export const mockedServices = { ...coreServiceMock, data: dataServiceMock, + cases: casesServiceMock, storage, unifiedSearch, triggersActionsUi: { 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 index d7e8ac41356d15..75da839c73dc08 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/indicators_page_wrapper.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/indicators_page_wrapper.tsx @@ -7,21 +7,39 @@ import React, { VFC } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { CasesPermissions } from '@kbn/cases-plugin/common'; import { IndicatorsPage } from '../modules/indicators/pages'; import { IntegrationsGuard } from './integrations_guard/integrations_guard'; import { SecuritySolutionPluginTemplateWrapper } from './security_solution_plugin_template_wrapper'; +import { useKibana } from '../hooks'; + +// export const APP_ID = 'threatIntdelligence'; +export const APP_ID = 'securitySolution'; export const IndicatorsPageWrapper: VFC = () => { + const { cases } = useKibana().services; + const CasesContext = cases.ui.getCasesContext(); + const permissions: CasesPermissions = { + all: true, + create: true, + read: true, + update: true, + delete: true, + push: true, + }; + const queryClient = new QueryClient(); return ( - - - - - - - + + + + + + + + + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/__snapshots__/add_to_existing_case.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/__snapshots__/add_to_existing_case.test.tsx.snap new file mode 100644 index 00000000000000..1b112dc036dc37 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/__snapshots__/add_to_existing_case.test.tsx.snap @@ -0,0 +1,185 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AddToExistingCase should render an EuiContextMenuItem 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+ +
+ , + "container":
+ +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`AddToExistingCase should render the EuiContextMenuItem disabled 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+ +
+ , + "container":
+ +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/add_to_existing_case.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/add_to_existing_case.stories.tsx new file mode 100644 index 00000000000000..097718fcdbdb80 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/add_to_existing_case.stories.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 { Story } from '@storybook/react'; +import { EuiContextMenuPanel } from '@elastic/eui'; +import { StoryProvidersComponent } from '../../../../common/mocks/story_providers'; +import { generateMockUrlIndicator } from '../../../../../common/types/indicator'; +import { AddToExistingCase } from './add_to_existing_case'; + +export default { + title: 'AddToExistingCase', +}; + +const mockIndicator = generateMockUrlIndicator(); + +export const Default: Story = () => { + const items = [ + window.alert('Clicked')} />, + ]; + + return ( + + + + ); +}; + +export const Disabled: Story = () => { + const fields = { ...mockIndicator.fields }; + delete fields['threat.indicator.name']; + const mockIndicatorMissingName = { + _id: mockIndicator._id, + fields, + }; + + const items = [ + window.alert('Clicked')} + />, + ]; + + return ( + + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/add_to_existing_case.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/add_to_existing_case.test.tsx new file mode 100644 index 00000000000000..626e37b82a46ab --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/add_to_existing_case.test.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 React from 'react'; +import { render } from '@testing-library/react'; +import { AddToExistingCase } from './add_to_existing_case'; +import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; +import { generateMockFileIndicator, Indicator } from '../../../../../common/types/indicator'; + +describe('AddToExistingCase', () => { + it('should render an EuiContextMenuItem', () => { + const indicator: Indicator = generateMockFileIndicator(); + const onClick = () => window.alert('clicked'); + const component = render( + + + + ); + expect(component).toMatchSnapshot(); + }); + + it('should render the EuiContextMenuItem disabled', () => { + const indicator: Indicator = generateMockFileIndicator(); + const fields = { ...indicator.fields }; + delete fields['threat.indicator.name']; + const indicatorMissingName = { + _id: indicator._id, + fields, + }; + const onClick = () => window.alert('clicked'); + const component = render( + + + + ); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/add_to_existing_case.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/add_to_existing_case.tsx new file mode 100644 index 00000000000000..1b620cde1a75be --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/add_to_existing_case.tsx @@ -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 React, { VFC } from 'react'; +import { EuiContextMenuItem } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; +import { EMPTY_VALUE } from '../../../../common/constants'; +import { + AttachmentMetadata, + generateAttachmentsMetadata, + generateAttachmentsWithoutOwner, +} from '../../utils/attachments'; +import { useKibana } from '../../../../hooks/use_kibana'; +import { Indicator } from '../../../../../common/types/indicator'; + +export interface AddToExistingCaseProps { + /** + * Indicator used to generate an attachment to an existing case + */ + indicator: Indicator; + /** + * Click event to close the popover in the parent component + */ + onClick: () => void; + /** + * Used for unit and e2e tests. + */ + ['data-test-subj']?: string; +} + +/** + * Leverages the cases plugin api to display a modal listing all the existing cases. + * Once a case is selected, an attachment is added to it and a confirmation snackbar + * presents a link to view the case. + * + * This component renders an {@link EuiContextMenu}. + * + * @returns add to existing case for a context menu + */ +export const AddToExistingCase: VFC = ({ + indicator, + onClick, + 'data-test-subj': dataTestSubj, +}) => { + const { cases } = useKibana().services; + const selectCaseModal = cases.hooks.getUseCasesAddToExistingCaseModal({}); + + const id: string = indicator._id as string; + const attachmentMetadata: AttachmentMetadata = generateAttachmentsMetadata(indicator); + const attachments: CaseAttachmentsWithoutOwner = generateAttachmentsWithoutOwner( + id, + attachmentMetadata + ); + + // disable the item if there isn't an indicator name + // in the case's attachment, the indicator name is the link to open the flyout + const disabled: boolean = attachmentMetadata.indicatorName === EMPTY_VALUE; + + const menuItemClicked = () => { + onClick(); + selectCaseModal.open({ attachments }); + }; + + return ( + menuItemClicked()} + data-test-subj={dataTestSubj} + disabled={disabled} + > + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/index.ts b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/index.ts new file mode 100644 index 00000000000000..fa4ac0f96ae3c8 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_existing_case/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 './add_to_existing_case'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/__snapshots__/add_to_new_case.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/__snapshots__/add_to_new_case.test.tsx.snap new file mode 100644 index 00000000000000..ec30f6d767e261 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/__snapshots__/add_to_new_case.test.tsx.snap @@ -0,0 +1,185 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AddToNewCase should render an EuiContextMenuItem 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+ +
+ , + "container":
+ +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`AddToNewCase should render the EuiContextMenuItem disabled 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+ +
+ , + "container":
+ +
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/add_to_new_case.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/add_to_new_case.stories.tsx new file mode 100644 index 00000000000000..65940111829996 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/add_to_new_case.stories.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Story } from '@storybook/react'; +import { EuiContextMenuPanel } from '@elastic/eui'; +import { AddToNewCase } from './add_to_new_case'; +import { StoryProvidersComponent } from '../../../../common/mocks/story_providers'; +import { generateMockUrlIndicator } from '../../../../../common/types/indicator'; + +export default { + title: 'AddToNewCase', +}; + +const mockIndicator = generateMockUrlIndicator(); + +export const Default: Story = () => { + const items = [ + window.alert('Clicked')} />, + ]; + + return ( + + + + ); +}; + +export const Disabled: Story = () => { + const fields = { ...mockIndicator.fields }; + delete fields['threat.indicator.name']; + const mockIndicatorMissingName = { + _id: mockIndicator._id, + fields, + }; + const items = [ + window.alert('Clicked')} />, + ]; + + return ( + + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/add_to_new_case.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/add_to_new_case.test.tsx new file mode 100644 index 00000000000000..44cce6e064a0c8 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/add_to_new_case.test.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render } from '@testing-library/react'; +import React from 'react'; +import { generateMockFileIndicator, Indicator } from '../../../../../common/types/indicator'; +import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; +import { AddToNewCase } from './add_to_new_case'; + +describe('AddToNewCase', () => { + it('should render an EuiContextMenuItem', () => { + const indicator: Indicator = generateMockFileIndicator(); + const onClick = () => window.alert('clicked'); + const component = render( + + + + ); + expect(component).toMatchSnapshot(); + }); + it('should render the EuiContextMenuItem disabled', () => { + const indicator: Indicator = generateMockFileIndicator(); + const fields = { ...indicator.fields }; + delete fields['threat.indicator.name']; + const indicatorMissingName = { + _id: indicator._id, + fields, + }; + const onClick = () => window.alert('clicked'); + const component = render( + + + + ); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/add_to_new_case.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/add_to_new_case.tsx new file mode 100644 index 00000000000000..abe2687787229e --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/add_to_new_case.tsx @@ -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 React, { VFC } from 'react'; +import { EuiContextMenuItem } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; +import { EMPTY_VALUE } from '../../../../common/constants'; +import { + AttachmentMetadata, + generateAttachmentsMetadata, + generateAttachmentsWithoutOwner, +} from '../../utils/attachments'; +import { useKibana } from '../../../../hooks'; +import { Indicator } from '../../../../../common/types/indicator'; + +export interface AddToNewCaseProps { + /** + * Indicator used to generate an attachment to a new case + */ + indicator: Indicator; + /** + * Click event to close the popover in the parent component + */ + onClick: () => void; + /** + * Used for unit and e2e tests. + */ + ['data-test-subj']?: string; +} + +/** + * Leverages the cases plugin api to display a flyout to create a new case. + * Once a case is created, an attachment is added to it and a confirmation snackbar + * presents a link to view the case. + * + * This component renders an {@link EuiContextMenu}. + * + * @returns add to existing case for a context menu + */ +export const AddToNewCase: VFC = ({ + indicator, + onClick, + 'data-test-subj': dataTestSubj, +}) => { + const { cases } = useKibana().services; + const createCaseFlyout = cases.hooks.getUseCasesAddToNewCaseFlyout({}); + + const id: string = indicator._id as string; + const attachmentMetadata: AttachmentMetadata = generateAttachmentsMetadata(indicator); + const attachments: CaseAttachmentsWithoutOwner = generateAttachmentsWithoutOwner( + id, + attachmentMetadata + ); + + // disable the item if there isn't an indicator name + // in the case's attachment, the indicator name is the link to open the flyout + const disabled: boolean = attachmentMetadata.indicatorName === EMPTY_VALUE; + + const menuItemClicked = () => { + onClick(); + createCaseFlyout.open({ attachments }); + }; + + return ( + menuItemClicked()} + data-test-subj={dataTestSubj} + disabled={disabled} + > + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/index.ts b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/index.ts new file mode 100644 index 00000000000000..9df27fe4a3f541 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/add_to_new_case/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 './add_to_new_case'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/attachment_children.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/attachment_children.tsx new file mode 100644 index 00000000000000..d48da8a546ddf1 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/attachment_children.tsx @@ -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 React from 'react'; +import { ExternalReferenceAttachmentViewProps } from '@kbn/cases-plugin/public/client/attachment_framework/types'; +import { AttachmentMetadata } from '../../utils/attachments'; +import { CommentChildren } from './comment_children/comment_children'; + +/** + * Component lazy loaded when creating a new attachment type that will be registered + * as an external reference. + * The component is then shown in the Cases view. + * It renders some text and a flyout. + */ +export const initComponent = () => { + return (props: ExternalReferenceAttachmentViewProps) => { + const indicatorId: string = props.externalReferenceId; + const metadata = props.externalReferenceMetadata as unknown as AttachmentMetadata; + + return ; + }; +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/__snapshots__/comment_children.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/__snapshots__/comment_children.test.tsx.snap new file mode 100644 index 00000000000000..83e7487e1185e1 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/__snapshots__/comment_children.test.tsx.snap @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`attachment_children initComponent should show loading 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+ , + "container":
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/comment_children.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/comment_children.stories.tsx new file mode 100644 index 00000000000000..eaf41e943592b2 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/comment_children.stories.tsx @@ -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 React from 'react'; +import { Story } from '@storybook/react'; +import { of } from 'rxjs'; +import { IKibanaSearchResponse } from '@kbn/data-plugin/common'; +import { CommentChildren } from './comment_children'; +import { StoryProvidersComponent } from '../../../../../common/mocks/story_providers'; +import { AttachmentMetadata } from '../../../utils'; + +export default { + title: 'CommentChildren', +}; + +export const Default: Story = () => { + const id: string = '123'; + const metadata: AttachmentMetadata = { + indicatorName: 'indicatorName', + indicatorFeedName: 'indicatorFeedName', + indicatorType: 'indicatorType', + indicatorFirstSeen: 'indicatorFirstSeen', + }; + + const response: IKibanaSearchResponse = { + isRunning: false, + isPartial: false, + rawResponse: { + hits: { + hits: [ + { + prop1: 'prop1', + prop2: 'prop2', + }, + ], + }, + }, + }; + const kibana = { + data: { + search: { + search: () => of(response), + }, + }, + }; + + return ( + + + + ); +}; + +export const Loading: Story = () => { + const id: string = '123'; + const metadata: AttachmentMetadata = { + indicatorName: 'indicatorName', + indicatorFeedName: 'indicatorFeedName', + indicatorType: 'indicatorType', + indicatorFirstSeen: 'indicatorFirstSeen', + }; + + const response: IKibanaSearchResponse = { + isRunning: true, + isPartial: true, + rawResponse: {}, + }; + const kibana = { + data: { + search: { + search: () => of(response), + }, + }, + }; + + return ( + + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/comment_children.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/comment_children.test.tsx new file mode 100644 index 00000000000000..95e2371904d7ea --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/comment_children.test.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + CommentChildren, + INDICATOR_FEED_NAME_TEST_ID, + INDICATOR_NAME_TEST_ID, + INDICATOR_TYPE_TEST_ID, +} from './comment_children'; +import { AttachmentMetadata } from '../../../utils'; +import { TestProvidersComponent } from '../../../../../common/mocks/test_providers'; +import { useIndicatorById } from '../../../hooks'; + +jest.mock('../../../hooks/use_indicator_by_id'); + +describe('attachment_children initComponent', () => { + it('should render the basic values', () => { + const id: string = 'abc123'; + const metadata: AttachmentMetadata = { + indicatorName: 'indicatorName', + indicatorFeedName: 'indicatorFeedName', + indicatorType: 'indicatorType', + indicatorFirstSeen: 'indicatorFirstSeen', + }; + + (useIndicatorById as jest.MockedFunction).mockReturnValue({ + indicator: { + prop1: 'prop1', + prop2: 'prop2', + }, + isLoading: false, + }); + + const { getByTestId } = render( + + + + ); + expect(getByTestId(INDICATOR_NAME_TEST_ID)).toHaveTextContent(metadata.indicatorName); + expect(getByTestId(INDICATOR_FEED_NAME_TEST_ID)).toHaveTextContent(metadata.indicatorFeedName); + expect(getByTestId(INDICATOR_TYPE_TEST_ID)).toHaveTextContent(metadata.indicatorType); + }); + + it('should show loading', () => { + const id: string = 'abc123'; + const metadata: AttachmentMetadata = { + indicatorName: 'indicatorName', + indicatorFeedName: 'indicatorFeedName', + indicatorType: 'indicatorType', + indicatorFirstSeen: 'indicatorFirstSeen', + }; + + (useIndicatorById as jest.MockedFunction).mockReturnValue({ + indicator: {}, + isLoading: true, + }); + + const component = render( + + + + ); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/comment_children.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/comment_children.tsx new file mode 100644 index 00000000000000..bee88c15aee53b --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/comment_children.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo, useState, VFC } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiLoadingLogo, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useStyles } from '../styles'; +import { useIndicatorById } from '../../../hooks'; +import { AttachmentMetadata } from '../../../utils'; +import { CasesFlyout } from '../flyout'; + +export const INDICATOR_NAME_TEST_ID = 'tiCasesIndicatorName'; +export const INDICATOR_FEED_NAME_TEST_ID = 'tiCasesIndicatorFeedName'; +export const INDICATOR_TYPE_TEST_ID = 'tiCasesIndicatorTYPE'; + +export interface CommentChildrenProps { + /** + * Id of the document (indicator) to be fetched + */ + id: string; + /** + * Metadata saved in the case attachment (indicator) + */ + metadata: AttachmentMetadata; +} + +/** + * Renders some basic values (indicator name, type and feed name) in the comment section + * of the case attachment. Also renders a flyout for more details about the indicator. + */ +export const CommentChildren: VFC = ({ id, metadata }) => { + const styles = useStyles(); + const [expanded, setExpanded] = useState(false); + + const { indicator, isLoading } = useIndicatorById(id); + + const { indicatorName, indicatorType, indicatorFeedName } = metadata; + + const flyoutFragment = useMemo( + () => + expanded ? ( + } + closeFlyout={() => setExpanded(false)} + /> + ) : null, + [expanded, indicator, metadata] + ); + + if (isLoading) { + return ; + } + + return ( + <> + + + + + + + + + + + + setExpanded(true)}> + {indicatorName} + {' '} + + + + + + + + + + + + + + +

{indicatorFeedName}

+
+
+
+ + + + + + + + + + + +

{indicatorType}

+
+
+
+
+ + {flyoutFragment} + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/index.ts b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/index.ts new file mode 100644 index 00000000000000..cec70b9a4062a1 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/comment_children/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 './comment_children'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/flyout/__snapshots__/flyout.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/flyout/__snapshots__/flyout.test.tsx.snap new file mode 100644 index 00000000000000..d8da6e4afe13c8 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/components/attachment_children/flyout/__snapshots__/flyout.test.tsx.snap @@ -0,0 +1,206 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CasesFlyout should render flyout with json details 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+ +
+
+
+ , + "container":
+
+
+ +
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/take_action/take_action.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/take_action/take_action.test.tsx new file mode 100644 index 00000000000000..9f4379b648213e --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/take_action/take_action.test.tsx @@ -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 { render } from '@testing-library/react'; +import React from 'react'; +import { generateMockFileIndicator, Indicator } from '../../../../../../common/types/indicator'; +import { TestProvidersComponent } from '../../../../../common/mocks/test_providers'; +import { TakeAction } from './take_action'; + +describe('TakeAction', () => { + it('should render an EuiContextMenuPanel', () => { + const indicator: Indicator = generateMockFileIndicator(); + const component = render( + + + + ); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/take_action/take_action.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/take_action/take_action.tsx new file mode 100644 index 00000000000000..33184860354799 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/take_action/take_action.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, VFC } from 'react'; +import { EuiButton, EuiContextMenuPanel, EuiPopover, useGeneratedHtmlId } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { AddToNewCase } from '../../../../cases/components/add_to_new_case/add_to_new_case'; +import { AddToExistingCase } from '../../../../cases/components/add_to_existing_case/add_to_existing_case'; +import { Indicator } from '../../../../../../common/types/indicator'; +import { InvestigateInTimelineContextMenu } from '../../../../timeline'; + +export const TAKE_ACTION_BUTTON_TEST_ID = 'tiIndicatorFlyoutTakeActionButton'; +export const INVESTIGATE_IN_TIMELINE_CONTEXT_MENU_TEST_ID = + 'tiIndicatorFlyoutInvestigateInTimelineContextMenu'; +export const ADD_TO_EXISTING_CASE_CONTEXT_MENU_TEST_ID = + 'tiIndicatorFlyoutAddToExistingCaseContextMenu'; +export const ADD_TO_NEW_CASE_CONTEXT_MENU_TEST_ID = 'tiIndicatorFlyoutAddToNewCaseContextMenu'; + +export interface TakeActionProps { + /** + * Indicator object + */ + indicator: Indicator; +} + +/** + * Component rendered at the bottom of the indicators flyout + */ +export const TakeAction: VFC = ({ indicator }) => { + const [isPopoverOpen, setPopover] = useState(false); + const smallContextMenuPopoverId = useGeneratedHtmlId({ + prefix: 'smallContextMenuPopover', + }); + + const closePopover = () => { + setPopover(false); + }; + + const items = [ + , + , + , + ]; + + const button = ( + setPopover(!isPopoverOpen)}> + + + ); + + return ( + + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/actions_row_cell.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/actions_row_cell.tsx index 1add89ee4e205b..629bba793f1169 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/actions_row_cell.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/actions_row_cell.tsx @@ -7,6 +7,7 @@ import React, { useContext, VFC } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { MoreActions } from './more_actions/more_actions'; import { InvestigateInTimelineButtonIcon } from '../../../../timeline'; import { Indicator } from '../../../../../../common/types/indicator'; import { OpenIndicatorFlyoutButton } from './open_flyout_button'; @@ -35,6 +36,9 @@ export const ActionsRowCell: VFC<{ indicator: Indicator }> = ({ indicator }) => + + +
); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/more_actions/__snapshots__/more_actions.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/more_actions/__snapshots__/more_actions.test.tsx.snap new file mode 100644 index 00000000000000..20faa5bee623b7 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/more_actions/__snapshots__/more_actions.test.tsx.snap @@ -0,0 +1,118 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MoreActions should render an EuiContextMenuPanel 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+
+
+ + + +
+
+
+ , + "container":
+
+
+ + + +
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/more_actions/more_actions.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/more_actions/more_actions.test.tsx new file mode 100644 index 00000000000000..5b680a2f228e88 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/more_actions/more_actions.test.tsx @@ -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 { render } from '@testing-library/react'; +import React from 'react'; +import { generateMockFileIndicator, Indicator } from '../../../../../../../common/types/indicator'; +import { TestProvidersComponent } from '../../../../../../common/mocks/test_providers'; +import { MoreActions } from './more_actions'; + +describe('MoreActions', () => { + it('should render an EuiContextMenuPanel', () => { + const indicator: Indicator = generateMockFileIndicator(); + const component = render( + + + + ); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/more_actions/more_actions.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/more_actions/more_actions.tsx new file mode 100644 index 00000000000000..0292cea6180154 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/more_actions/more_actions.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, VFC } from 'react'; +import { + EuiButtonIcon, + EuiContextMenuPanel, + EuiPopover, + EuiToolTip, + useGeneratedHtmlId, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { AddToNewCase } from '../../../../../cases/components/add_to_new_case/add_to_new_case'; +import { AddToExistingCase } from '../../../../../cases/components/add_to_existing_case/add_to_existing_case'; +import { Indicator } from '../../../../../../../common/types/indicator'; + +export const MORE_ACTIONS_BUTTON_TEST_ID = 'tiIndicatorTableMoreActionsButton'; +export const ADD_TO_EXISTING_CASE_CONTEXT_MENU_TEST_ID = + 'tiIndicatorTableAddToExistingCaseContextMenu'; +export const ADD_TO_NEW_CASE_CONTEXT_MENU_TEST_ID = 'tiIndicatorTableAddToNewCaseContextMenu'; + +const BUTTON_LABEL = i18n.translate('xpack.threatIntelligence.indicator.table.moreActions', { + defaultMessage: 'More actions', +}); + +export interface TakeActionProps { + /** + * Indicator object + */ + indicator: Indicator; +} + +/** + * Component rendered in the action column. + * Renders a ... icon button, with a dropdown. + */ +export const MoreActions: VFC = ({ indicator }) => { + const [isPopoverOpen, setPopover] = useState(false); + const smallContextMenuPopoverId = useGeneratedHtmlId({ + prefix: 'smallContextMenuPopover', + }); + + const closePopover = () => { + setPopover(false); + }; + + const items = [ + , + , + ]; + + const button = ( + + setPopover((prevIsPopoverOpen) => !prevIsPopoverOpen)} + style={{ height: '100%' }} + data-test-subj={MORE_ACTIONS_BUTTON_TEST_ID} + /> + + ); + + return ( + + + + ); +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx index 3f920b42d43b9d..47ad42847c3d6f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx @@ -97,7 +97,7 @@ export const IndicatorsTable: VFC = ({ () => [ { id: 'Actions', - width: 72, + width: 84, headerCellRender: () => (
, "container":
, diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx index 08fe4b782c2c03..26b928b2ff0b24 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.stories.tsx @@ -7,9 +7,10 @@ import React from 'react'; import { Story } from '@storybook/react'; +import { EuiContextMenuPanel } from '@elastic/eui'; import { StoryProvidersComponent } from '../../../../common/mocks/story_providers'; import { generateMockUrlIndicator } from '../../../../../common/types/indicator'; -import { InvestigateInTimelineButton, InvestigateInTimelineButtonIcon } from '.'; +import { InvestigateInTimelineContextMenu, InvestigateInTimelineButtonIcon } from '.'; export default { title: 'InvestigateInTimeline', @@ -17,10 +18,12 @@ export default { const mockIndicator = generateMockUrlIndicator(); -export const Button: Story = () => { +export const ContextMenu: Story = () => { + const items = []; + return ( - + ); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx index 81850d049d830b..62e5f2c13ea54d 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.test.tsx @@ -13,7 +13,7 @@ import { Indicator, } from '../../../../../common/types/indicator'; import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; -import { InvestigateInTimelineButton, InvestigateInTimelineButtonIcon } from '.'; +import { InvestigateInTimelineContextMenu, InvestigateInTimelineButtonIcon } from '.'; import { EMPTY_VALUE } from '../../../../common/constants'; describe('', () => { @@ -24,7 +24,7 @@ describe('', () => { const component = render( - + ); @@ -38,7 +38,7 @@ describe('', () => { const component = render( - + ); diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx index c99c02ac8f8a22..2e92491c09390a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/investigate_in_timeline/investigate_in_timeline.tsx @@ -6,7 +6,7 @@ */ import React, { VFC } from 'react'; -import { EuiButton, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import { EuiButtonIcon, EuiContextMenuItem, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { useInvestigateInTimeline } from '../../hooks'; @@ -19,11 +19,15 @@ const BUTTON_ICON_LABEL: string = i18n.translate( } ); -export interface InvestigateInTimelineButtonProps { +export interface InvestigateInTimelineProps { /** * Value passed to the timeline. Used in combination with field if is type of {@link Indicator}. */ data: Indicator; + /** + * Click event to close the popover in the parent component + */ + onClick?: () => void; /** * Used for unit and e2e tests. */ @@ -34,12 +38,13 @@ export interface InvestigateInTimelineButtonProps { * Investigate in timeline button, uses the InvestigateInTimelineAction component (x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx) * retrieved from the SecuritySolutionContext. * - * This component renders an {@link EuiButton}. + * This component renders an {@link EuiContextMenu}. * - * @returns add to timeline button + * @returns investigate in timeline for a context menu */ -export const InvestigateInTimelineButton: VFC = ({ +export const InvestigateInTimelineContextMenu: VFC = ({ data, + onClick, 'data-test-subj': dataTestSub, }) => { const { investigateInTimelineFn } = useInvestigateInTimeline({ indicator: data }); @@ -47,13 +52,22 @@ export const InvestigateInTimelineButton: VFC return <>; } + const menuItemClicked = () => { + if (onClick) onClick(); + investigateInTimelineFn(); + }; + return ( - + menuItemClicked()} + data-test-subj={dataTestSub} + > - + ); }; @@ -65,7 +79,7 @@ export const InvestigateInTimelineButton: VFC * * @returns add to timeline button icon */ -export const InvestigateInTimelineButtonIcon: VFC = ({ +export const InvestigateInTimelineButtonIcon: VFC = ({ data, 'data-test-subj': dataTestSub, }) => { diff --git a/x-pack/plugins/threat_intelligence/public/plugin.tsx b/x-pack/plugins/threat_intelligence/public/plugin.tsx index 5ed76e02817430..6fc058131af9b8 100755 --- a/x-pack/plugins/threat_intelligence/public/plugin.tsx +++ b/x-pack/plugins/threat_intelligence/public/plugin.tsx @@ -5,15 +5,18 @@ * 2.0. */ -import { CoreStart, Plugin } from '@kbn/core/public'; +import { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { Provider as ReduxStoreProvider } from 'react-redux'; import React, { Suspense } from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { ExternalReferenceAttachmentType } from '@kbn/cases-plugin/public/client/attachment_framework/types'; +import { generateAttachmentType } from './modules/cases/utils'; import { KibanaContextProvider } from './hooks/use_kibana'; import { SecuritySolutionPluginContext, Services, + SetupPlugins, ThreatIntelligencePluginSetup, ThreatIntelligencePluginStart, ThreatIntelligencePluginStartDeps, @@ -52,7 +55,13 @@ export const createApp = ); export class ThreatIntelligencePlugin implements Plugin { - public async setup(): Promise { + public async setup( + core: CoreSetup, + plugins: SetupPlugins + ): Promise { + const externalAttachmentType: ExternalReferenceAttachmentType = generateAttachmentType(); + plugins.cases.attachmentFramework.registerExternalReference(externalAttachmentType); + return {}; } diff --git a/x-pack/plugins/threat_intelligence/public/types.ts b/x-pack/plugins/threat_intelligence/public/types.ts index 33d49e2d68c159..673a44494ac189 100644 --- a/x-pack/plugins/threat_intelligence/public/types.ts +++ b/x-pack/plugins/threat_intelligence/public/types.ts @@ -21,6 +21,7 @@ import { BrowserField } from '@kbn/rule-registry-plugin/common'; import { Store } from 'redux'; import { DataProvider } from '@kbn/timelines-plugin/common'; import { Start as InspectorPluginStart } from '@kbn/inspector-plugin/public'; +import { CasesUiSetup, CasesUiStart } from '@kbn/cases-plugin/public/types'; export interface SecuritySolutionDataViewBase extends DataViewBase { fields: Array; @@ -29,6 +30,10 @@ export interface SecuritySolutionDataViewBase extends DataViewBase { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ThreatIntelligencePluginSetup {} +export interface SetupPlugins { + cases: CasesUiSetup; +} + export interface ThreatIntelligencePluginStart { getComponent: () => (props: { securitySolutionContext: SecuritySolutionPluginContext; @@ -40,6 +45,7 @@ export interface ThreatIntelligencePluginStartDeps { } export type Services = { + cases: CasesUiStart; data: DataPublicPluginStart; storage: Storage; dataViews: DataViewsPublicPluginStart; diff --git a/x-pack/plugins/threat_intelligence/tsconfig.json b/x-pack/plugins/threat_intelligence/tsconfig.json index ccdf417105b16b..7298fbe3c987d2 100644 --- a/x-pack/plugins/threat_intelligence/tsconfig.json +++ b/x-pack/plugins/threat_intelligence/tsconfig.json @@ -16,6 +16,7 @@ "../../../typings/**/*" ], "kbn_references": [ + { "path": "../cases/tsconfig.json" }, { "path": "../timelines/tsconfig.json" }, { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts index dfdc1ed3eabd4a..a5856169a57489 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts @@ -32,7 +32,7 @@ export * from './events'; export type TimelineFactoryQueryTypes = TimelineEventsQueries; export interface TimelineRequestBasicOptions extends IEsSearchRequest { - timerange: TimerangeInput; + timerange?: TimerangeInput; filterQuery: ESQuery | string | undefined; defaultIndex: string[]; factoryQueryType?: TimelineFactoryQueryTypes; diff --git a/x-pack/plugins/timelines/common/types/timeline/cells/index.ts b/x-pack/plugins/timelines/common/types/timeline/cells/index.ts index cc9cec70d8751f..52130cf52354d2 100644 --- a/x-pack/plugins/timelines/common/types/timeline/cells/index.ts +++ b/x-pack/plugins/timelines/common/types/timeline/cells/index.ts @@ -30,4 +30,5 @@ export type CellValueElementProps = EuiDataGridCellValueElementProps & { scopeId: string; truncate?: boolean; key?: string; + closeCellPopover?: () => void; }; diff --git a/x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts b/x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts index d706aff6f6aa7a..4aaa675137fc8c 100644 --- a/x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts +++ b/x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts @@ -13,8 +13,11 @@ export const IS_OPERATOR = ':'; /** The `exists` operator in a KQL query */ export const EXISTS_OPERATOR = ':*'; +/** The `is one of` operator in a KQL query */ +export const IS_ONE_OF_OPERATOR = 'includes'; + /** The operator applied to a field */ -export type QueryOperator = ':' | ':*'; +export type QueryOperator = typeof IS_OPERATOR | typeof EXISTS_OPERATOR | typeof IS_ONE_OF_OPERATOR; export enum DataProviderType { default = 'default', @@ -24,7 +27,7 @@ export enum DataProviderType { export interface QueryMatch { field: string; displayField?: string; - value: string | number; + value: string | number | Array; displayValue?: string | number; operator: QueryOperator; } diff --git a/x-pack/plugins/timelines/public/components/inspect/index.tsx b/x-pack/plugins/timelines/public/components/inspect/index.tsx index a174cc08a83ee1..304dd8cdfcf8b6 100644 --- a/x-pack/plugins/timelines/public/components/inspect/index.tsx +++ b/x-pack/plugins/timelines/public/components/inspect/index.tsx @@ -95,7 +95,7 @@ const InspectButtonComponent: React.FC = ({ data-test-subj="inspect-icon-button" iconSize="m" iconType="inspect" - isDisabled={loading || isDisabled || false} + isDisabled={loading || isDisabled} title={i18n.INSPECT} onClick={handleClick} /> diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index 23998a25fabd28..a3b8ff5457d8f0 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -140,7 +140,7 @@ const EmptyHeaderCellRender: ComponentType = () => null; const gridStyle: EuiDataGridStyle = { border: 'none', fontSize: 's', header: 'underline' }; -const EuiDataGridContainer = styled.div<{ hideLastPage: boolean }>` +const EuiEventTableContainer = styled.div<{ hideLastPage: boolean }>` ul.euiPagination__list { li.euiPagination__item:last-child { ${({ hideLastPage }) => `${hideLastPage ? 'display:none' : ''}`}; @@ -732,6 +732,7 @@ export const BodyComponent = React.memo( setEventsDeleted, hasAlertsCrudPermissions, ]); + const closeCellPopoverAction = dataGridRef.current?.closeCellPopover; const columnsWithCellActions: EuiDataGridColumn[] = useMemo( () => columnHeaders.map((header) => { @@ -743,7 +744,7 @@ export const BodyComponent = React.memo( header: columnHeaders.find((h) => h.id === header.id), pageSize, scopeId: id, - closeCellPopover: dataGridRef.current?.closeCellPopover, + closeCellPopover: closeCellPopoverAction, }); return { ...header, @@ -782,6 +783,7 @@ export const BodyComponent = React.memo( dispatch, id, pageSize, + closeCellPopoverAction, ] ); @@ -833,6 +835,7 @@ export const BodyComponent = React.memo( setCellProps, scopeId: id, truncate: isDetails ? false : true, + closeCellPopover: closeCellPopoverAction, }) as React.ReactElement; }; return Cell; @@ -846,6 +849,7 @@ export const BodyComponent = React.memo( renderCellValue, rowRenderers, theme, + closeCellPopoverAction, ]); const onChangeItemsPerPage = useCallback( @@ -873,7 +877,7 @@ export const BodyComponent = React.memo( <> {tableView === 'gridView' && ( - ES_LIMIT_COUNT}> + ES_LIMIT_COUNT}> ( }} ref={dataGridRef} /> - + )} {tableView === 'eventRenderedView' && ( - + ES_LIMIT_COUNT}> + + )} diff --git a/x-pack/plugins/timelines/public/components/t_grid/helpers.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/helpers.test.tsx index 3ddebe460a554e..f68d67339d19a6 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/helpers.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/helpers.test.tsx @@ -8,12 +8,21 @@ import { cloneDeep } from 'lodash/fp'; import { Filter, EsQueryConfig, FilterStateStore } from '@kbn/es-query'; -import { DataProviderType } from '../../../common/types/timeline'; import { + DataProviderType, + EXISTS_OPERATOR, + IS_ONE_OF_OPERATOR, + IS_OPERATOR, +} from '../../../common/types/timeline'; +import { + buildExistsQueryMatch, buildGlobalQuery, + buildIsOneOfQueryMatch, + buildIsQueryMatch, combineQueries, getDefaultViewSelection, isSelectableView, + isStringOrNumberArray, isViewSelection, resolverIsShowing, } from './helpers'; @@ -682,7 +691,7 @@ describe('Combined Queries', () => { }); invalidViewSelections.forEach((value) => { - test(`it returns false when value is INvalid: ${value}`, () => { + test(`it returns false when value is invalid: ${value}`, () => { expect(isViewSelection(value)).toBe(false); }); }); @@ -699,9 +708,9 @@ describe('Combined Queries', () => { }); }); - describe('given INvalid values', () => { + describe('given invalid values', () => { invalidViewSelections.forEach((value) => { - test(`it ALWAYS returns 'gridView' for NON-selectable timelineId ${timelineId}, with INvalid value: ${value}`, () => { + test(`it ALWAYS returns 'gridView' for NON-selectable timelineId ${timelineId}, with invalid value: ${value}`, () => { expect(getDefaultViewSelection({ timelineId, value })).toEqual('gridView'); }); }); @@ -722,7 +731,7 @@ describe('Combined Queries', () => { describe('given INvalid values', () => { invalidViewSelections.forEach((value) => { - test(`it ALWAYS returns 'gridView' for selectable timelineId ${timelineId}, with INvalid value: ${value}`, () => { + test(`it ALWAYS returns 'gridView' for selectable timelineId ${timelineId}, with invalid value: ${value}`, () => { expect(getDefaultViewSelection({ timelineId, value })).toEqual('gridView'); }); }); @@ -730,4 +739,290 @@ describe('Combined Queries', () => { }); }); }); + describe('DataProvider yields same result as kqlQuery equivolent with each operator', () => { + describe('IS ONE OF operator', () => { + test('dataprovider matches kql equivolent', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].queryMatch.operator = IS_ONE_OF_OPERATOR; + dataProviders[0].queryMatch.value = ['a', 'b', 'c']; + const { filterQuery: filterQueryWithDataProvider } = combineQueries({ + config, + dataProviders, + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: '', language: 'kuery' }, + kqlMode: 'search', + })!; + const { filterQuery: filterQueryWithKQLQuery } = combineQueries({ + config, + dataProviders: [], + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: 'name: ("a" OR "b" OR "c")', language: 'kuery' }, + kqlMode: 'search', + })!; + + expect(filterQueryWithDataProvider).toEqual(filterQueryWithKQLQuery); + }); + test('dataprovider with negated IS ONE OF operator matches kql equivolent', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].queryMatch.operator = IS_ONE_OF_OPERATOR; + dataProviders[0].queryMatch.value = ['a', 'b', 'c']; + dataProviders[0].excluded = true; + const { filterQuery: filterQueryWithDataProvider } = combineQueries({ + config, + dataProviders, + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: '', language: 'kuery' }, + kqlMode: 'search', + })!; + const { filterQuery: filterQueryWithKQLQuery } = combineQueries({ + config, + dataProviders: [], + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: 'NOT name: ("a" OR "b" OR "c")', language: 'kuery' }, + kqlMode: 'search', + })!; + + expect(filterQueryWithDataProvider).toEqual(filterQueryWithKQLQuery); + }); + }); + describe('IS operator', () => { + test('dataprovider matches kql equivolent', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].queryMatch.operator = IS_OPERATOR; + dataProviders[0].queryMatch.value = 'a'; + const { filterQuery: filterQueryWithDataProvider } = combineQueries({ + config, + dataProviders, + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: '', language: 'kuery' }, + kqlMode: 'search', + })!; + const { filterQuery: filterQueryWithKQLQuery } = combineQueries({ + config, + dataProviders: [], + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: 'name: "a"', language: 'kuery' }, + kqlMode: 'search', + })!; + + expect(filterQueryWithDataProvider).toEqual(filterQueryWithKQLQuery); + }); + test('dataprovider with negated IS operator matches kql equivolent', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].queryMatch.operator = IS_OPERATOR; + dataProviders[0].queryMatch.value = 'a'; + dataProviders[0].excluded = true; + const { filterQuery: filterQueryWithDataProvider } = combineQueries({ + config, + dataProviders, + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: '', language: 'kuery' }, + kqlMode: 'search', + })!; + const { filterQuery: filterQueryWithKQLQuery } = combineQueries({ + config, + dataProviders: [], + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: 'NOT name: "a"', language: 'kuery' }, + kqlMode: 'search', + })!; + + expect(filterQueryWithDataProvider).toEqual(filterQueryWithKQLQuery); + }); + }); + describe('Exists operator', () => { + test('dataprovider matches kql equivolent', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].queryMatch.operator = EXISTS_OPERATOR; + const { filterQuery: filterQueryWithDataProvider } = combineQueries({ + config, + dataProviders, + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: '', language: 'kuery' }, + kqlMode: 'search', + })!; + const { filterQuery: filterQueryWithKQLQuery } = combineQueries({ + config, + dataProviders: [], + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: 'name : *', language: 'kuery' }, + kqlMode: 'search', + })!; + + expect(filterQueryWithDataProvider).toEqual(filterQueryWithKQLQuery); + }); + test('dataprovider with negated EXISTS operator matches kql equivolent', () => { + const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); + dataProviders[0].queryMatch.operator = EXISTS_OPERATOR; + dataProviders[0].excluded = true; + const { filterQuery: filterQueryWithDataProvider } = combineQueries({ + config, + dataProviders, + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: '', language: 'kuery' }, + kqlMode: 'search', + })!; + const { filterQuery: filterQueryWithKQLQuery } = combineQueries({ + config, + dataProviders: [], + indexPattern: mockIndexPattern, + browserFields: mockBrowserFields, + filters: [], + kqlQuery: { query: 'NOT name : *', language: 'kuery' }, + kqlMode: 'search', + })!; + + expect(filterQueryWithDataProvider).toEqual(filterQueryWithKQLQuery); + }); + }); + }); +}); + +describe('isStringOrNumberArray', () => { + test('it returns false when value is not an array', () => { + expect(isStringOrNumberArray('just a string')).toBe(false); + }); + + test('it returns false when value is an array of mixed types', () => { + expect(isStringOrNumberArray(['mixed', 123, 'types'])).toBe(false); + }); + + test('it returns false when value is an array of bad values', () => { + const badValues = [undefined, null, {}] as unknown as string[]; + expect(isStringOrNumberArray(badValues)).toBe(false); + }); + + test('it returns true when value is an empty array', () => { + expect(isStringOrNumberArray([])).toBe(true); + }); + + test('it returns true when value is an array of all strings', () => { + expect(isStringOrNumberArray(['all', 'string', 'values'])).toBe(true); + }); + + test('it returns true when value is an array of all numbers', () => { + expect(isStringOrNumberArray([123, 456, 789])).toBe(true); + }); +}); + +describe('buildExistsQueryMatch', () => { + it('correcty computes EXISTS query with no nested field', () => { + expect( + buildExistsQueryMatch({ isFieldTypeNested: false, field: 'host', browserFields: {} }) + ).toBe(`host ${EXISTS_OPERATOR}`); + }); + + it('correcty computes EXISTS query with nested field', () => { + expect( + buildExistsQueryMatch({ + isFieldTypeNested: true, + field: 'nestedField.firstAttributes', + browserFields: mockBrowserFields, + }) + ).toBe(`nestedField: { firstAttributes: * }`); + }); +}); + +describe('buildIsQueryMatch', () => { + it('correcty computes IS query with no nested field', () => { + expect( + buildIsQueryMatch({ + isFieldTypeNested: false, + field: 'nestedField.thirdAttributes', + value: 100000, + browserFields: {}, + }) + ).toBe(`nestedField.thirdAttributes ${IS_OPERATOR} 100000`); + }); + + it('correcty computes IS query with nested date field', () => { + expect( + buildIsQueryMatch({ + isFieldTypeNested: true, + browserFields: mockBrowserFields, + field: 'nestedField.thirdAttributes', + value: 1668521970232, + }) + ).toBe(`nestedField: { thirdAttributes${IS_OPERATOR} \"1668521970232\" }`); + }); + + it('correcty computes IS query with nested string field', () => { + expect( + buildIsQueryMatch({ + isFieldTypeNested: true, + browserFields: mockBrowserFields, + field: 'nestedField.secondAttributes', + value: 'text', + }) + ).toBe(`nestedField: { secondAttributes${IS_OPERATOR} text }`); + }); +}); + +describe('buildIsOneOfQueryMatch', () => { + it('correcty computes IS ONE OF query with numbers', () => { + expect( + buildIsOneOfQueryMatch({ + field: 'kibana.alert.worflow_status', + value: [1, 2, 3], + }) + ).toBe('kibana.alert.worflow_status : (1 OR 2 OR 3)'); + }); + + it('correcty computes IS ONE OF query with strings', () => { + expect( + buildIsOneOfQueryMatch({ + field: 'kibana.alert.worflow_status', + value: ['a', 'b', 'c'], + }) + ).toBe(`kibana.alert.worflow_status : (\"a\" OR \"b\" OR \"c\")`); + }); + + it('correcty computes IS ONE OF query if value is an empty array', () => { + expect( + buildIsOneOfQueryMatch({ + field: 'kibana.alert.worflow_status', + value: [], + }) + ).toBe("kibana.alert.worflow_status : ''"); + }); + + it('correcty computes IS ONE OF query if given a single string value', () => { + expect( + buildIsOneOfQueryMatch({ + field: 'kibana.alert.worflow_status', + value: ['a'], + }) + ).toBe(`kibana.alert.worflow_status : (\"a\")`); + }); + + it('correcty computes IS ONE OF query if given a single numeric value', () => { + expect( + buildIsOneOfQueryMatch({ + field: 'kibana.alert.worflow_status', + value: [1], + }) + ).toBe(`kibana.alert.worflow_status : (1)`); + }); }); diff --git a/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx b/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx index cb925fff4d39c5..64043dbefcef67 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/helpers.tsx @@ -12,13 +12,14 @@ import memoizeOne from 'memoize-one'; import { elementOrChildrenHasFocus } from '../../../common/utils/accessibility'; import type { BrowserFields } from '../../../common/search_strategy/index_fields'; import { - DataProvider, - DataProvidersAnd, DataProviderType, EXISTS_OPERATOR, + IS_ONE_OF_OPERATOR, + IS_OPERATOR, } from '../../../common/types/timeline'; +import type { DataProvider, DataProvidersAnd } from '../../../common/types/timeline'; +import { assertUnreachable } from '../../../common/utility_types'; import { convertToBuildEsQuery, escapeQueryValue } from '../utils/keury'; - import { EVENTS_TABLE_CLASS_NAME } from './styles'; import { TableId } from '../../types'; import { ViewSelection } from './event_rendered_view/selector'; @@ -33,7 +34,7 @@ interface CombineQueries { kqlMode: string; } -const isNumber = (value: string | number) => !isNaN(Number(value)); +const isNumber = (value: string | number): value is number => !isNaN(Number(value)); const convertDateFieldToQuery = (field: string, value: string | number) => `${field}: ${isNumber(value) ? value : new Date(value).valueOf()}`; @@ -96,27 +97,41 @@ const checkIfFieldTypeIsNested = (field: string, browserFields: BrowserFields) = const buildQueryMatch = ( dataProvider: DataProvider | DataProvidersAnd, browserFields: BrowserFields -) => - `${dataProvider.excluded ? 'NOT ' : ''}${ - dataProvider.queryMatch.operator !== EXISTS_OPERATOR && - dataProvider.type !== DataProviderType.template - ? checkIfFieldTypeIsNested(dataProvider.queryMatch.field, browserFields) - ? convertNestedFieldToQuery( - dataProvider.queryMatch.field, - dataProvider.queryMatch.value, - browserFields - ) - : checkIfFieldTypeIsDate(dataProvider.queryMatch.field, browserFields) - ? convertDateFieldToQuery(dataProvider.queryMatch.field, dataProvider.queryMatch.value) - : `${dataProvider.queryMatch.field} : ${ - isNumber(dataProvider.queryMatch.value) - ? dataProvider.queryMatch.value - : escapeQueryValue(dataProvider.queryMatch.value) - }` - : checkIfFieldTypeIsNested(dataProvider.queryMatch.field, browserFields) - ? convertNestedFieldToExistQuery(dataProvider.queryMatch.field, browserFields) - : `${dataProvider.queryMatch.field} ${EXISTS_OPERATOR}` - }`.trim(); +) => { + const { + excluded, + type, + queryMatch: { field, operator, value }, + } = dataProvider; + + const isFieldTypeNested = checkIfFieldTypeIsNested(field, browserFields); + const isExcluded = excluded ? 'NOT ' : ''; + + switch (operator) { + case IS_OPERATOR: + if (!isStringOrNumberArray(value)) { + return `${isExcluded}${ + type !== DataProviderType.template + ? buildIsQueryMatch({ browserFields, field, isFieldTypeNested, value }) + : buildExistsQueryMatch({ browserFields, field, isFieldTypeNested }) + }`; + } else { + return `${isExcluded}${field} : ${JSON.stringify(value[0])}`; + } + + case EXISTS_OPERATOR: + return `${isExcluded}${buildExistsQueryMatch({ browserFields, field, isFieldTypeNested })}`; + + case IS_ONE_OF_OPERATOR: + if (isStringOrNumberArray(value)) { + return `${isExcluded}${buildIsOneOfQueryMatch({ field, value })}`; + } else { + return `${isExcluded}${field} : ${JSON.stringify(value)}`; + } + default: + assertUnreachable(operator); + } +}; export const buildGlobalQuery = (dataProviders: DataProvider[], browserFields: BrowserFields) => dataProviders @@ -276,3 +291,57 @@ export const getDefaultViewSelection = ({ /** This local storage key stores the `Grid / Event rendered view` selection */ export const ALERTS_TABLE_VIEW_SELECTION_KEY = 'securitySolution.alerts.table.view-selection'; + +export const buildIsQueryMatch = ({ + browserFields, + field, + isFieldTypeNested, + value, +}: { + browserFields: BrowserFields; + field: string; + isFieldTypeNested: boolean; + value: string | number; +}): string => { + if (isFieldTypeNested) { + return convertNestedFieldToQuery(field, value, browserFields); + } else if (checkIfFieldTypeIsDate(field, browserFields)) { + return convertDateFieldToQuery(field, value); + } else { + return `${field} : ${isNumber(value) ? value : escapeQueryValue(value)}`; + } +}; + +export const buildExistsQueryMatch = ({ + browserFields, + field, + isFieldTypeNested, +}: { + browserFields: BrowserFields; + field: string; + isFieldTypeNested: boolean; +}): string => { + return isFieldTypeNested + ? convertNestedFieldToExistQuery(field, browserFields) + : `${field} ${EXISTS_OPERATOR}`; +}; + +export const buildIsOneOfQueryMatch = ({ + field, + value, +}: { + field: string; + value: Array; +}): string => { + const trimmedField = field.trim(); + if (value.length) { + return `${trimmedField} : (${value + .map((item) => (isNumber(item) ? Number(item) : `${escapeQueryValue(item.trim())}`)) + .join(' OR ')})`; + } + return `${trimmedField} : ''`; +}; + +export const isStringOrNumberArray = (value: unknown): value is Array => + Array.isArray(value) && + (value.every((x) => typeof x === 'string') || value.every((x) => typeof x === 'number')); diff --git a/x-pack/plugins/timelines/public/mock/browser_fields.ts b/x-pack/plugins/timelines/public/mock/browser_fields.ts index 1e6afa11fa138f..d852c0002e83b4 100644 --- a/x-pack/plugins/timelines/public/mock/browser_fields.ts +++ b/x-pack/plugins/timelines/public/mock/browser_fields.ts @@ -810,6 +810,22 @@ export const mockBrowserFields: BrowserFields = { }, }, }, + 'nestedField.thirdAttributes': { + aggregatable: false, + category: 'nestedField', + description: '', + example: '', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'nestedField.thirdAttributes', + searchable: true, + type: 'date', + subType: { + nested: { + path: 'nestedField', + }, + }, + }, }, }, }; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts index 5424568c44a704..e9a2ef7e49cda9 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.ts @@ -27,17 +27,19 @@ export const buildEqlDsl = (options: TimelineEqlRequestOptions): Record = { // eslint-disable-next-line prefer-const let { fieldRequested, ...queryOptions } = cloneDeep(options); queryOptions.fields = buildFieldsRequest(fieldRequested, queryOptions.excludeEcsData); + const { activePage, querySize } = options.pagination; const producerBuckets = getOr([], 'aggregations.producers.buckets', response.rawResponse); const totalCount = response.rawResponse.hits.total || 0; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts index 9f6902c65f6391..5ae88bcf6f460b 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts @@ -28,7 +28,6 @@ export const buildTimelineEventsAllQuery = ({ timerange, }: Omit) => { const filterClause = [...createQueryFilterClauses(filterQuery)]; - const getTimerangeFilter = (timerangeOption: TimerangeInput | undefined): TimerangeFilter[] => { if (timerangeOption) { const { to, from } = timerangeOption; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/constants.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/constants.ts index b795e921f07cd0..574418ed2758fd 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/constants.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/constants.ts @@ -59,6 +59,7 @@ export const TIMELINE_EVENTS_FIELDS = [ ALERT_RISK_SCORE, 'kibana.alert.threshold_result', 'kibana.alert.building_block_type', + 'kibana.alert.suppression.docs_count', 'event.code', 'event.module', 'event.action', diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index dfa54cb8df3d58..afbc7a4274f8a6 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -6787,9 +6787,6 @@ "xpack.apm.compositeSpanCallsLabel": ", {count} appels, sur une moyenne de {duration}", "xpack.apm.correlations.ccsWarningCalloutBody": "Les données pour l'analyse de corrélation n'ont pas pu être totalement récupérées. Cette fonctionnalité est prise en charge uniquement à partir des versions {version} et ultérieures.", "xpack.apm.correlations.failedTransactions.helpPopover.basicExplanation": "Les corrélations vous aident à découvrir les attributs qui ont le plus d'influence pour distinguer les échecs et les succès d'une transaction. Les transactions sont considérées comme un échec lorsque leur valeur {field} est {value}.", - "xpack.apm.correlations.fieldContextPopover.addFilterAriaLabel": "Filtrer sur le {fieldName} : \"{value}\"", - "xpack.apm.correlations.fieldContextPopover.calculatedFromSampleDescription": "Calculé à partir d'un échantillon de {sampleSize} documents", - "xpack.apm.correlations.fieldContextPopover.removeFilterAriaLabel": "Exclure le {fieldName} : \"{value}\"", "xpack.apm.correlations.progressTitle": "Progression : {progress} %", "xpack.apm.durationDistribution.chart.percentileMarkerLabel": "{markerPercentile}e centile", "xpack.apm.durationDistributionChart.totalSpansCount": "Total : {totalDocCount} {totalDocCount, plural, one {intervalle} other {intervalles}}", @@ -7093,7 +7090,6 @@ "xpack.apm.correlations.failedTransactions.panelTitle": "Distribution de la latence des transactions ayant échoué", "xpack.apm.correlations.failedTransactions.tableTitle": "Corrélations", "xpack.apm.correlations.fieldContextPopover.descriptionTooltipContent": "Afficher le top 10 des valeurs de champ", - "xpack.apm.correlations.fieldContextPopover.fieldTopValuesLabel": "Top 10 des valeurs", "xpack.apm.correlations.fieldContextPopover.notTopTenValueMessage": "Le terme sélectionné n'est pas dans le top 10", "xpack.apm.correlations.fieldContextPopover.topFieldValuesAriaLabel": "Afficher le top 10 des valeurs de champ", "xpack.apm.correlations.highImpactText": "Élevé", @@ -9934,7 +9930,6 @@ "xpack.csp.rules.ruleFlyout.remediationTabLabel": "Résolution", "xpack.csp.rules.rulesPageHeader.benchmarkIntegrationsButtonLabel": "Intégrations Benchmark", "xpack.csp.rules.rulesTable.cisSectionColumnLabel": "Section CIS", - "xpack.csp.rules.rulesTable.lastModifiedColumnLabel": "Dernière modification", "xpack.csp.rules.rulesTable.nameColumnLabel": "Nom", "xpack.csp.rules.rulesTable.searchPlaceholder": "Recherche", "xpack.dataVisualizer.choroplethMap.topValuesCount": "Compte des valeurs les plus élevées pour {fieldName}", @@ -9945,8 +9940,6 @@ "xpack.dataVisualizer.dataGrid.field.metricDistributionChart.tooltipValueBetweenLabel": "{percent} % des documents ont des valeurs comprises entre {minValFormatted} et {maxValFormatted}", "xpack.dataVisualizer.dataGrid.field.metricDistributionChart.tooltipValueEqualLabel": "{percent} % des documents ont une valeur de {valFormatted}", "xpack.dataVisualizer.dataGrid.field.removeFilterAriaLabel": "Exclure le {fieldName} : \"{value}\"", - "xpack.dataVisualizer.dataGrid.field.topValues.calculatedFromSampleDescription": "Calculé à partir d'un échantillon de {topValuesSamplerShardSize} documents par partition", - "xpack.dataVisualizer.dataGrid.fieldExpandedRow.choroplethMapTopValues.calculatedFromSampleDescription": "Calculé à partir d'un échantillon de {topValuesSamplerShardSize} documents par partition", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.numberContent.displayingPercentilesLabel": "Affichage de {minPercent} - {maxPercent} centiles", "xpack.dataVisualizer.dataGrid.fieldText.fieldMayBePopulatedDescription": "Il peut être rempli, par exemple, à l'aide d'un paramètre {copyToParam} dans le mapping du document ou être réduit à partir du champ {sourceParam} après une indexation par l'utilisation des paramètres {includesParam} et {excludesParam}.", "xpack.dataVisualizer.dataGrid.fieldText.fieldNotPresentDescription": "Ce champ n'était pas présent dans le champ {sourceParam} des documents interrogés.", @@ -9984,7 +9977,6 @@ "xpack.dataVisualizer.nameCollisionMsg": "\"{name}\" existe déjà, veuillez fournir un nom unique", "xpack.dataVisualizer.randomSamplerSettingsPopUp.probabilityLabel": "Probabilité utilisée : {samplingProbability} %", "xpack.dataVisualizer.searchPanel.ofFieldsTotal": "sur un total de {totalCount}", - "xpack.dataVisualizer.searchPanel.sampleSizeOptionLabel": "Taille de l'échantillon (par partition) : {wrappedValue}", "xpack.dataVisualizer.searchPanel.totalDocCountLabel": "Total des documents : {prepend}{strongTotalCount}", "xpack.dataVisualizer.searchPanel.totalDocCountNumber": "{totalCount, plural, other {#}}", "xpack.dataVisualizer.addCombinedFieldsLabel": "Ajouter un champ combiné", @@ -10017,8 +10009,6 @@ "xpack.dataVisualizer.dataGrid.field.loadingLabel": "Chargement", "xpack.dataVisualizer.dataGrid.field.metricDistributionChart.seriesName": "distribution", "xpack.dataVisualizer.dataGrid.field.topValuesLabel": "Valeurs les plus élevées", - "xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.falseCountLabel": "faux", - "xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.trueCountLabel": "vrai", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.countLabel": "compte", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.distinctValueLabel": "valeurs distinctes", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.metaTableTitle": "Statistiques des documents", @@ -10263,13 +10253,10 @@ "xpack.dataVisualizer.removeCombinedFieldsLabel": "Retirer le champ combiné", "xpack.dataVisualizer.samplingOptionsButton": "Options d’échantillonnage", "xpack.dataVisualizer.searchPanel.allFieldsLabel": "Tous les champs", - "xpack.dataVisualizer.searchPanel.allOptionLabel": "Tout rechercher", "xpack.dataVisualizer.searchPanel.invalidSyntax": "Syntaxe non valide", "xpack.dataVisualizer.searchPanel.numberFieldsLabel": "Champs de numéros", - "xpack.dataVisualizer.searchPanel.queryBarPlaceholder": "La sélection d'une taille d'échantillon plus petite réduira les temps d'exécution de la requête et la charge sur le cluster.", "xpack.dataVisualizer.searchPanel.queryBarPlaceholderText": "Rechercher… (par exemple, status:200 AND extension:\"PHP\")", "xpack.dataVisualizer.searchPanel.randomSamplerMessage": "Des valeurs approximatives sont indiquées dans le décompte de documents et le graphique, qui utilisent des agrégations par échantillonnage aléatoire.", - "xpack.dataVisualizer.searchPanel.sampleSizeAriaLabel": "Sélectionner le nombre de documents à échantillonner", "xpack.dataVisualizer.searchPanel.showEmptyFields": "Afficher les champs vides", "xpack.dataVisualizer.title": "Charger un fichier", "xpack.embeddableEnhanced.actions.panelNotifications.manyDrilldowns": "Le panneau comporte {count} recherches", @@ -10360,13 +10347,59 @@ "xpack.enterpriseSearch.content.shared.result.header.metadata.icon.ariaLabel": "Métadonnées pour le document : {id}", "xpack.enterpriseSearch.crawler.action.deleteDomain.confirmationPopupMessage": "Voulez-vous vraiment supprimer le domaine \"{domainUrl}\" et tous ses paramètres ?", "xpack.enterpriseSearch.crawler.addDomainForm.entryPointLabel": "Le point d'entrée du robot d'indexation a été défini sur {entryPointValue}", - "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.formDescription": "Configurer l'indexation automatisée. {readMoreMessage}.", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "{crawlType} indexation sur {domainCount, plural, one {# domaine} other {# domaines}}", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "Inclure les plans de site découverts dans {robotsDotTxt}", "xpack.enterpriseSearch.crawler.crawlRulesTable.description": "Créez une règle d'indexation pour inclure ou exclure les pages dont l'URL correspond à la règle. Les règles sont exécutées dans l'ordre séquentiel, et chaque URL est évaluée en fonction de la première correspondance. {link}", "xpack.enterpriseSearch.crawler.deduplicationPanel.description": "Le robot d'indexation n'indexe que les pages uniques. Choisissez les champs que le robot d'indexation doit utiliser lorsqu'il recherche les pages en double. Désélectionnez tous les champs de schéma pour autoriser les documents en double dans ce domaine. {documentationLink}.", "xpack.enterpriseSearch.crawler.deleteDomainModal.description": "Supprimer le domaine {domainUrl} de votre robot d'indexation. Cela supprimera également tous les points d'entrée et toutes les règles d'indexation que vous avez configurés. Tous les documents associés à ce domaine seront supprimés lors de la prochaine indexation. {thisCannotBeUndoneMessage}", "xpack.enterpriseSearch.crawler.entryPointsTable.emptyMessageDescription": "{link} pour spécifier un point d'entrée pour le robot d'indexation", + "xpack.enterpriseSearch.cronEditor.cronDaily.fieldHour.textAtLabel": "À", + "xpack.enterpriseSearch.cronEditor.cronDaily.fieldTimeLabel": "Heure", + "xpack.enterpriseSearch.cronEditor.cronDaily.hourSelectLabel": "Heure", + "xpack.enterpriseSearch.cronEditor.cronDaily.minuteSelectLabel": "Minute", + "xpack.enterpriseSearch.cronEditor.cronHourly.fieldMinute.textAtLabel": "À", + "xpack.enterpriseSearch.cronEditor.cronHourly.fieldTimeLabel": "Minute", + "xpack.enterpriseSearch.cronEditor.cronMonthly.fieldDateLabel": "Date", + "xpack.enterpriseSearch.cronEditor.cronMonthly.fieldHour.textAtLabel": "À", + "xpack.enterpriseSearch.cronEditor.cronMonthly.fieldTimeLabel": "Heure", + "xpack.enterpriseSearch.cronEditor.cronMonthly.hourSelectLabel": "Heure", + "xpack.enterpriseSearch.cronEditor.cronMonthly.minuteSelectLabel": "Minute", + "xpack.enterpriseSearch.cronEditor.cronMonthly.textOnTheLabel": "Le", + "xpack.enterpriseSearch.cronEditor.cronWeekly.fieldDateLabel": "Jour", + "xpack.enterpriseSearch.cronEditor.cronWeekly.fieldHour.textAtLabel": "À", + "xpack.enterpriseSearch.cronEditor.cronWeekly.fieldTimeLabel": "Heure", + "xpack.enterpriseSearch.cronEditor.cronWeekly.hourSelectLabel": "Heure", + "xpack.enterpriseSearch.cronEditor.cronWeekly.minuteSelectLabel": "Minute", + "xpack.enterpriseSearch.cronEditor.cronWeekly.textOnLabel": "Le", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldDate.textOnTheLabel": "Le", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldDateLabel": "Date", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldHour.textAtLabel": "À", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldMonth.textInLabel": "En", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldMonthLabel": "Mois", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldTimeLabel": "Heure", + "xpack.enterpriseSearch.cronEditor.cronYearly.hourSelectLabel": "Heure", + "xpack.enterpriseSearch.cronEditor.cronYearly.minuteSelectLabel": "Minute", + "xpack.enterpriseSearch.cronEditor.day.friday": "vendredi", + "xpack.enterpriseSearch.cronEditor.day.monday": "lundi", + "xpack.enterpriseSearch.cronEditor.day.saturday": "samedi", + "xpack.enterpriseSearch.cronEditor.day.sunday": "dimanche", + "xpack.enterpriseSearch.cronEditor.day.thursday": "jeudi", + "xpack.enterpriseSearch.cronEditor.day.tuesday": "mardi", + "xpack.enterpriseSearch.cronEditor.day.wednesday": "mercredi", + "xpack.enterpriseSearch.cronEditor.fieldFrequencyLabel": "Fréquence", + "xpack.enterpriseSearch.cronEditor.month.april": "avril", + "xpack.enterpriseSearch.cronEditor.month.august": "août", + "xpack.enterpriseSearch.cronEditor.month.december": "décembre", + "xpack.enterpriseSearch.cronEditor.month.february": "février", + "xpack.enterpriseSearch.cronEditor.month.january": "janvier", + "xpack.enterpriseSearch.cronEditor.month.july": "juillet", + "xpack.enterpriseSearch.cronEditor.month.june": "juin", + "xpack.enterpriseSearch.cronEditor.month.march": "mars", + "xpack.enterpriseSearch.cronEditor.month.may": "mai", + "xpack.enterpriseSearch.cronEditor.month.november": "novembre", + "xpack.enterpriseSearch.cronEditor.month.october": "octobre", + "xpack.enterpriseSearch.cronEditor.month.september": "septembre", + "xpack.enterpriseSearch.cronEditor.textEveryLabel": "Chaque", "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "Les nœuds Enterprise Search fonctionnent-ils dans votre déploiement cloud ? {deploymentSettingsLink}", "xpack.enterpriseSearch.errorConnectingState.description1": "Impossible d'établir une connexion à Enterprise Search avec l'URL hôte {enterpriseSearchUrl} en raison de l'erreur suivante :", "xpack.enterpriseSearch.errorConnectingState.description2": "Vérifiez que l'URL hôte est correctement configurée dans {configFile}.", @@ -11398,21 +11431,21 @@ "xpack.enterpriseSearch.content.newIndex.steps.configureIngestion.title": "Configurer les paramètres d’ingestion", "xpack.enterpriseSearch.content.newIndex.steps.createIndex.crawler.title": "Indexer avec le robot d'indexation", "xpack.enterpriseSearch.content.newIndex.steps.createIndex.title": "Créer un index Elasticsearch", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.chineseDropDownOptionLabel": "Chinois", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.danishDropDownOptionLabel": "Danois", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.dutchDropDownOptionLabel": "Néerlandais", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.englishDropDownOptionLabel": "Anglais", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.frenchDropDownOptionLabel": "Français", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.germanDropDownOptionLabel": "Allemand", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.italianDropDownOptionLabel": "Italien", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.japaneseDropDownOptionLabel": "Japonais", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.koreanDropDownOptionLabel": "Coréen", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.portugueseBrazilDropDownOptionLabel": "Portugais (Brésil)", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.portugueseDropDownOptionLabel": "Portugais", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.russianDropDownOptionLabel": "Russe", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.spanishDropDownOptionLabel": "Espagnol", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.thaiDropDownOptionLabel": "Thaï", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.universalDropDownOptionLabel": "Universel", + "xpack.enterpriseSearch.content.supportedLanguages.chineseLabel": "Chinois", + "xpack.enterpriseSearch.content.supportedLanguages.danishLabel": "Danois", + "xpack.enterpriseSearch.content.supportedLanguages.dutchLabel": "Néerlandais", + "xpack.enterpriseSearch.content.supportedLanguages.englishLabel": "Anglais", + "xpack.enterpriseSearch.content.supportedLanguages.frenchLabel": "Français", + "xpack.enterpriseSearch.content.supportedLanguages.germanLabel": "Allemand", + "xpack.enterpriseSearch.content.supportedLanguages.italianLabel": "Italien", + "xpack.enterpriseSearch.content.supportedLanguages.japaneseLabel": "Japonais", + "xpack.enterpriseSearch.content.supportedLanguages.koreanLabel": "Coréen", + "xpack.enterpriseSearch.content.supportedLanguages.portugueseBrazilLabel": "Portugais (Brésil)", + "xpack.enterpriseSearch.content.supportedLanguages.portugueseLabel": "Portugais", + "xpack.enterpriseSearch.content.supportedLanguages.russianLabel": "Russe", + "xpack.enterpriseSearch.content.supportedLanguages.spanishLabel": "Espagnol", + "xpack.enterpriseSearch.content.supportedLanguages.thaiLabel": "Thaï", + "xpack.enterpriseSearch.content.supportedLanguages.universalLabel": "Universel", "xpack.enterpriseSearch.content.newIndex.types.api": "Point de terminaison d'API", "xpack.enterpriseSearch.content.newIndex.types.connector": "Connecteur", "xpack.enterpriseSearch.content.newIndex.types.crawler": "Robot d'indexation", @@ -11502,13 +11535,10 @@ "xpack.enterpriseSearch.crawler.addDomainForm.urlLabel": "URL de domaine", "xpack.enterpriseSearch.crawler.addDomainForm.validateButtonLabel": "Valider le domaine", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.crawlAutomaticallySwitchLabel": "Indexer automatiquement", - "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.crawlUnitsPrefix": "Chaque", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.readMoreLink": "En lire plus.", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.scheduleDescription": "Le calendrier d’indexation effectuera une indexation complète de chaque domaine de cet index.", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.scheduleFrequencyLabel": "Planifier la fréquence", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.scheduleUnitsLabel": "Planifier des unités de temps", - "xpack.enterpriseSearch.crawler.automaticCrawlScheduler.disableCrawlSchedule.successMessage": "L'indexation automatique a été désactivée.", - "xpack.enterpriseSearch.crawler.automaticCrawlScheduler.submitCrawlSchedule.successMessage": "Votre planification d'indexation automatique a été mise à jour.", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlDepthLabel": "Profondeur maximale de l'indexation", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlTypeLabel": "Type d'indexation", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.customEntryPointUrlsTextboxLabel": "URL de points d'entrée personnalisés", @@ -19475,7 +19505,6 @@ "xpack.ml.aiops.explainLogRateSpikes.docTitle": "Expliquer les pics de taux de log", "xpack.ml.aiopsBreadcrumbLabel": "AIOps", "xpack.ml.aiopsBreadcrumbs.explainLogRateSpikesLabel": "Expliquer les pics de taux de log", - "xpack.ml.aiopsBreadcrumbs.selectDateViewLabel": "Vue de données", "xpack.ml.alertConditionValidation.title": "La condition d'alerte contient les problèmes suivants :", "xpack.ml.alertContext.anomalyExplorerUrlDescription": "URL pour ouvrir dans Anomaly Explorer", "xpack.ml.alertContext.isInterimDescription": "Indique si les premiers résultats contiennent des résultats temporaires", @@ -31037,7 +31066,6 @@ "xpack.synthetics.monitorManagement.closeButtonLabel": "Fermer", "xpack.synthetics.monitorManagement.closeLabel": "Fermer", "xpack.synthetics.monitorManagement.completed": "TERMINÉ", - "xpack.synthetics.monitorManagement.confirmDescriptionLabel": "Cette action supprimera le moniteur mais conservera toute donnée collectée. Cette action ne peut pas être annulée.", "xpack.synthetics.monitorManagement.createAgentPolicy": "Créer une stratégie d'agent", "xpack.synthetics.monitorManagement.createMonitorLabel": "Créer le moniteur", "xpack.synthetics.monitorManagement.delete": "Supprimer l’emplacement", @@ -31098,12 +31126,10 @@ "xpack.synthetics.monitorManagement.monitorAdvancedOptions.namespaceHelpLearnMoreLabel": "En savoir plus", "xpack.synthetics.monitorManagement.monitorDeleteFailureMessage": "Impossible de supprimer le moniteur. Réessayez plus tard.", "xpack.synthetics.monitorManagement.monitorDeleteLoadingMessage": "Suppression du moniteur...", - "xpack.synthetics.monitorManagement.monitorDeleteSuccessMessage": "Moniteur supprimé.", "xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "Moniteur mis à jour.", "xpack.synthetics.monitorManagement.monitorFailureMessage": "Impossible d'enregistrer le moniteur. Réessayez plus tard.", "xpack.synthetics.monitorManagement.monitorList.actions": "Actions", "xpack.synthetics.monitorManagement.monitorList.enabled": "Activé", - "xpack.synthetics.monitorManagement.monitorList.enabled.tooltip": "Ce moniteur a été ajouté depuis un projet externe. Pour supprimer le moniteur, retirez-le du projet et retransmettez la configuration.", "xpack.synthetics.monitorManagement.monitorList.locations": "Emplacements", "xpack.synthetics.monitorManagement.monitorList.monitorName": "Nom de moniteur", "xpack.synthetics.monitorManagement.monitorList.monitorType": "Type de moniteur", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index bc02683bb84d32..c6fb419a5371fa 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6777,9 +6777,6 @@ "xpack.apm.compositeSpanCallsLabel": "、{count}件の呼び出し、平均{duration}", "xpack.apm.correlations.ccsWarningCalloutBody": "相関関係分析のデータを完全に取得できませんでした。この機能は{version}以降でのみサポートされています。", "xpack.apm.correlations.failedTransactions.helpPopover.basicExplanation": "相関関係では、トランザクションの失敗と成功を区別するうえで最も影響度が大きい属性を見つけることができます。{field}値が{value}のときには、トランザクションが失敗であると見なされます。", - "xpack.apm.correlations.fieldContextPopover.addFilterAriaLabel": "{fieldName}のフィルター:\"{value}\"", - "xpack.apm.correlations.fieldContextPopover.calculatedFromSampleDescription": "{sampleSize}ドキュメントのサンプルから計算済み", - "xpack.apm.correlations.fieldContextPopover.removeFilterAriaLabel": "{fieldName}の除外:\"{value}\"", "xpack.apm.correlations.progressTitle": "進捗状況: {progress}%", "xpack.apm.durationDistributionChart.totalSpansCount": "{totalDocCount}合計{totalDocCount, plural, other {個のスパン}}", "xpack.apm.durationDistributionChart.totalTransactionsCount": "{totalDocCount}合計{totalDocCount, plural, other {個のトランザクション}}", @@ -7081,7 +7078,6 @@ "xpack.apm.correlations.failedTransactions.panelTitle": "失敗したトランザクションの遅延分布", "xpack.apm.correlations.failedTransactions.tableTitle": "相関関係", "xpack.apm.correlations.fieldContextPopover.descriptionTooltipContent": "上位10フィールド値を表示", - "xpack.apm.correlations.fieldContextPopover.fieldTopValuesLabel": "上位10の値", "xpack.apm.correlations.fieldContextPopover.notTopTenValueMessage": "選択した用語は上位10件にありません", "xpack.apm.correlations.fieldContextPopover.topFieldValuesAriaLabel": "上位10フィールド値を表示", "xpack.apm.correlations.highImpactText": "高", @@ -9921,7 +9917,6 @@ "xpack.csp.rules.ruleFlyout.remediationTabLabel": "修正", "xpack.csp.rules.rulesPageHeader.benchmarkIntegrationsButtonLabel": "ベンチマーク統合", "xpack.csp.rules.rulesTable.cisSectionColumnLabel": "CISセクション", - "xpack.csp.rules.rulesTable.lastModifiedColumnLabel": "最終更新:", "xpack.csp.rules.rulesTable.nameColumnLabel": "名前", "xpack.csp.rules.rulesTable.searchPlaceholder": "検索", "xpack.dataVisualizer.choroplethMap.topValuesCount": "{fieldName}の上位の値件数", @@ -9932,8 +9927,6 @@ "xpack.dataVisualizer.dataGrid.field.metricDistributionChart.tooltipValueBetweenLabel": "{percent}% のドキュメントに {minValFormatted} から {maxValFormatted} の間の値があります", "xpack.dataVisualizer.dataGrid.field.metricDistributionChart.tooltipValueEqualLabel": "{percent}% のドキュメントに {valFormatted} の値があります", "xpack.dataVisualizer.dataGrid.field.removeFilterAriaLabel": "{fieldName}の除外:\"{value}\"", - "xpack.dataVisualizer.dataGrid.field.topValues.calculatedFromSampleDescription": "1 つのシャードにつき {topValuesSamplerShardSize} のドキュメントのサンプルで計算されています", - "xpack.dataVisualizer.dataGrid.fieldExpandedRow.choroplethMapTopValues.calculatedFromSampleDescription": "1 つのシャードにつき {topValuesSamplerShardSize} のドキュメントのサンプルで計算されています", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.numberContent.displayingPercentilesLabel": "{minPercent} - {maxPercent} パーセンタイルを表示中", "xpack.dataVisualizer.dataGrid.fieldText.fieldMayBePopulatedDescription": "たとえば、ドキュメントマッピングで {copyToParam} パラメーターを使ったり、{includesParam} と {excludesParam} パラメーターを使用してインデックスした後に {sourceParam} フィールドから切り取ったりして入力される場合があります。", "xpack.dataVisualizer.dataGrid.fieldText.fieldNotPresentDescription": "このフィールドはクエリが実行されたドキュメントの {sourceParam} フィールドにありませんでした。", @@ -9970,7 +9963,6 @@ "xpack.dataVisualizer.nameCollisionMsg": "「{name}」はすでに存在します。一意の名前を入力してください。", "xpack.dataVisualizer.randomSamplerSettingsPopUp.probabilityLabel": "使用された確率:{samplingProbability}%", "xpack.dataVisualizer.searchPanel.ofFieldsTotal": "合計 {totalCount}", - "xpack.dataVisualizer.searchPanel.sampleSizeOptionLabel": "サンプルサイズ(シャード単位):{wrappedValue}", "xpack.dataVisualizer.searchPanel.totalDocCountLabel": "合計ドキュメント数:{prepend}{strongTotalCount}", "xpack.dataVisualizer.searchPanel.totalDocCountNumber": "{totalCount, plural, other {#}}", "xpack.dataVisualizer.addCombinedFieldsLabel": "結合されたフィールドを追加", @@ -10003,8 +9995,6 @@ "xpack.dataVisualizer.dataGrid.field.loadingLabel": "読み込み中", "xpack.dataVisualizer.dataGrid.field.metricDistributionChart.seriesName": "分布", "xpack.dataVisualizer.dataGrid.field.topValuesLabel": "トップの値", - "xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.falseCountLabel": "false", - "xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.trueCountLabel": "true", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.countLabel": "カウント", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.distinctValueLabel": "固有の値", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.metaTableTitle": "ドキュメント統計情報", @@ -10249,13 +10239,10 @@ "xpack.dataVisualizer.removeCombinedFieldsLabel": "結合されたフィールドを削除", "xpack.dataVisualizer.samplingOptionsButton": "抽出オプション", "xpack.dataVisualizer.searchPanel.allFieldsLabel": "すべてのフィールド", - "xpack.dataVisualizer.searchPanel.allOptionLabel": "すべて検索", "xpack.dataVisualizer.searchPanel.invalidSyntax": "無効な構文", "xpack.dataVisualizer.searchPanel.numberFieldsLabel": "数値フィールド", - "xpack.dataVisualizer.searchPanel.queryBarPlaceholder": "小さいサンプルサイズを選択することで、クエリの実行時間を短縮しクラスターへの負荷を軽減できます。", "xpack.dataVisualizer.searchPanel.queryBarPlaceholderText": "検索…(例:status:200 AND extension:\"PHP\")", "xpack.dataVisualizer.searchPanel.randomSamplerMessage": "近似値は、ランダムサンプラーアグリゲーションを使用する、合計ドキュメント数およびグラフに表示されます。", - "xpack.dataVisualizer.searchPanel.sampleSizeAriaLabel": "サンプリングするドキュメント数を選択してください", "xpack.dataVisualizer.searchPanel.showEmptyFields": "空のフィールドを表示", "xpack.dataVisualizer.title": "ファイルをアップロード", "xpack.embeddableEnhanced.actions.panelNotifications.manyDrilldowns": "パネルには{count}個のドリルダウンがあります", @@ -10346,13 +10333,59 @@ "xpack.enterpriseSearch.content.shared.result.header.metadata.icon.ariaLabel": "ドキュメント{id}のメタデータ", "xpack.enterpriseSearch.crawler.action.deleteDomain.confirmationPopupMessage": "ドメイン\"{domainUrl}\"とすべての設定を削除しますか?", "xpack.enterpriseSearch.crawler.addDomainForm.entryPointLabel": "Webクローラーエントリポイントが{entryPointValue}として設定されました", - "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.formDescription": "自動クロールを設定します。{readMoreMessage}。", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "{domainCount, plural, other {# 件のドメイン}}で{crawlType}クロール", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "{robotsDotTxt}で検出されたサイトマップを含める", "xpack.enterpriseSearch.crawler.crawlRulesTable.description": "URLがルールと一致するページを含めるか除外するためのクロールルールを作成します。ルールは連続で実行されます。各URLは最初の一致に従って評価されます。{link}", "xpack.enterpriseSearch.crawler.deduplicationPanel.description": "Webクローラーは一意のページにのみインデックスします。重複するページを検討するときにクローラーが使用するフィールドを選択します。すべてのスキーマフィールドを選択解除して、このドメインで重複するドキュメントを許可します。{documentationLink}。", "xpack.enterpriseSearch.crawler.deleteDomainModal.description": "ドメイン{domainUrl}をクローラーから削除します。これにより、設定したすべてのエントリポイントとクロールルールも削除されます。このドメインに関連するすべてのドキュメントは、次回のクロールで削除されます。{thisCannotBeUndoneMessage}", "xpack.enterpriseSearch.crawler.entryPointsTable.emptyMessageDescription": "クローラーのエントリポイントを指定するには、{link}してください", + "xpack.enterpriseSearch.cronEditor.cronDaily.fieldHour.textAtLabel": "に", + "xpack.enterpriseSearch.cronEditor.cronDaily.fieldTimeLabel": "時間", + "xpack.enterpriseSearch.cronEditor.cronDaily.hourSelectLabel": "時間", + "xpack.enterpriseSearch.cronEditor.cronDaily.minuteSelectLabel": "分", + "xpack.enterpriseSearch.cronEditor.cronHourly.fieldMinute.textAtLabel": "に", + "xpack.enterpriseSearch.cronEditor.cronHourly.fieldTimeLabel": "分", + "xpack.enterpriseSearch.cronEditor.cronMonthly.fieldDateLabel": "日付", + "xpack.enterpriseSearch.cronEditor.cronMonthly.fieldHour.textAtLabel": "に", + "xpack.enterpriseSearch.cronEditor.cronMonthly.fieldTimeLabel": "時間", + "xpack.enterpriseSearch.cronEditor.cronMonthly.hourSelectLabel": "時間", + "xpack.enterpriseSearch.cronEditor.cronMonthly.minuteSelectLabel": "分", + "xpack.enterpriseSearch.cronEditor.cronMonthly.textOnTheLabel": "に", + "xpack.enterpriseSearch.cronEditor.cronWeekly.fieldDateLabel": "日", + "xpack.enterpriseSearch.cronEditor.cronWeekly.fieldHour.textAtLabel": "に", + "xpack.enterpriseSearch.cronEditor.cronWeekly.fieldTimeLabel": "時間", + "xpack.enterpriseSearch.cronEditor.cronWeekly.hourSelectLabel": "時間", + "xpack.enterpriseSearch.cronEditor.cronWeekly.minuteSelectLabel": "分", + "xpack.enterpriseSearch.cronEditor.cronWeekly.textOnLabel": "オン", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldDate.textOnTheLabel": "に", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldDateLabel": "日付", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldHour.textAtLabel": "に", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldMonth.textInLabel": "入", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldMonthLabel": "月", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldTimeLabel": "時間", + "xpack.enterpriseSearch.cronEditor.cronYearly.hourSelectLabel": "時間", + "xpack.enterpriseSearch.cronEditor.cronYearly.minuteSelectLabel": "分", + "xpack.enterpriseSearch.cronEditor.day.friday": "金曜日", + "xpack.enterpriseSearch.cronEditor.day.monday": "月曜日", + "xpack.enterpriseSearch.cronEditor.day.saturday": "土曜日", + "xpack.enterpriseSearch.cronEditor.day.sunday": "日曜日", + "xpack.enterpriseSearch.cronEditor.day.thursday": "木曜日", + "xpack.enterpriseSearch.cronEditor.day.tuesday": "火曜日", + "xpack.enterpriseSearch.cronEditor.day.wednesday": "水曜日", + "xpack.enterpriseSearch.cronEditor.fieldFrequencyLabel": "頻度", + "xpack.enterpriseSearch.cronEditor.month.april": "4 月", + "xpack.enterpriseSearch.cronEditor.month.august": "8 月", + "xpack.enterpriseSearch.cronEditor.month.december": "12 月", + "xpack.enterpriseSearch.cronEditor.month.february": "2 月", + "xpack.enterpriseSearch.cronEditor.month.january": "1 月", + "xpack.enterpriseSearch.cronEditor.month.july": "7 月", + "xpack.enterpriseSearch.cronEditor.month.june": "6 月", + "xpack.enterpriseSearch.cronEditor.month.march": "3 月", + "xpack.enterpriseSearch.cronEditor.month.may": "5月", + "xpack.enterpriseSearch.cronEditor.month.november": "11 月", + "xpack.enterpriseSearch.cronEditor.month.october": "10 月", + "xpack.enterpriseSearch.cronEditor.month.september": "9 月", + "xpack.enterpriseSearch.cronEditor.textEveryLabel": "毎", "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "クラウドデプロイのエンタープライズ サーチノードが実行中ですか?{deploymentSettingsLink}", "xpack.enterpriseSearch.errorConnectingState.description1": "次のエラーのため、ホストURL {enterpriseSearchUrl}では、エンタープライズ サーチへの接続を確立できません。", "xpack.enterpriseSearch.errorConnectingState.description2": "ホストURLが{configFile}で正しく構成されていることを確認してください。", @@ -11384,21 +11417,21 @@ "xpack.enterpriseSearch.content.newIndex.steps.configureIngestion.title": "インジェスチョン設定を構成", "xpack.enterpriseSearch.content.newIndex.steps.createIndex.crawler.title": "Webクローラーを使用してインデックス", "xpack.enterpriseSearch.content.newIndex.steps.createIndex.title": "Elasticsearchインデックスを作成", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.chineseDropDownOptionLabel": "中国語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.danishDropDownOptionLabel": "デンマーク語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.dutchDropDownOptionLabel": "オランダ語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.englishDropDownOptionLabel": "英語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.frenchDropDownOptionLabel": "フランス語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.germanDropDownOptionLabel": "ドイツ語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.italianDropDownOptionLabel": "イタリア語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.japaneseDropDownOptionLabel": "日本語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.koreanDropDownOptionLabel": "韓国語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.portugueseBrazilDropDownOptionLabel": "ポルトガル語(ブラジル)", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.portugueseDropDownOptionLabel": "ポルトガル語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.russianDropDownOptionLabel": "ロシア語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.spanishDropDownOptionLabel": "スペイン語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.thaiDropDownOptionLabel": "タイ語", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.universalDropDownOptionLabel": "ユニバーサル", + "xpack.enterpriseSearch.content.supportedLanguages.chineseLabel": "中国語", + "xpack.enterpriseSearch.content.supportedLanguages.danishLabel": "デンマーク語", + "xpack.enterpriseSearch.content.supportedLanguages.dutchLabel": "オランダ語", + "xpack.enterpriseSearch.content.supportedLanguages.englishLabel": "英語", + "xpack.enterpriseSearch.content.supportedLanguages.frenchLabel": "フランス語", + "xpack.enterpriseSearch.content.supportedLanguages.germanLabel": "ドイツ語", + "xpack.enterpriseSearch.content.supportedLanguages.italianLabel": "イタリア語", + "xpack.enterpriseSearch.content.supportedLanguages.japaneseLabel": "日本語", + "xpack.enterpriseSearch.content.supportedLanguages.koreanLabel": "韓国語", + "xpack.enterpriseSearch.content.supportedLanguages.portugueseBrazilLabel": "ポルトガル語(ブラジル)", + "xpack.enterpriseSearch.content.supportedLanguages.portugueseLabel": "ポルトガル語", + "xpack.enterpriseSearch.content.supportedLanguages.russianLabel": "ロシア語", + "xpack.enterpriseSearch.content.supportedLanguages.spanishLabel": "スペイン語", + "xpack.enterpriseSearch.content.supportedLanguages.thaiLabel": "タイ語", + "xpack.enterpriseSearch.content.supportedLanguages.universalLabel": "ユニバーサル", "xpack.enterpriseSearch.content.newIndex.types.api": "APIエンドポイント", "xpack.enterpriseSearch.content.newIndex.types.connector": "コネクター", "xpack.enterpriseSearch.content.newIndex.types.crawler": "Webクローラー", @@ -11488,13 +11521,10 @@ "xpack.enterpriseSearch.crawler.addDomainForm.urlLabel": "ドメインURL", "xpack.enterpriseSearch.crawler.addDomainForm.validateButtonLabel": "ドメインを検証", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.crawlAutomaticallySwitchLabel": "自動的にクロール", - "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.crawlUnitsPrefix": "毎", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.readMoreLink": "詳細をお読みください。", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.scheduleDescription": "クローリングスケジュールは、このインデックスのすべてのドメインに対してフルクローリングを実行します。", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.scheduleFrequencyLabel": "スケジュール頻度", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.scheduleUnitsLabel": "スケジュール時間単位", - "xpack.enterpriseSearch.crawler.automaticCrawlScheduler.disableCrawlSchedule.successMessage": "自動クローリングが無効にされました。", - "xpack.enterpriseSearch.crawler.automaticCrawlScheduler.submitCrawlSchedule.successMessage": "自動クローリングスケジュールが更新されました。", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlDepthLabel": "最大クロール深度", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlTypeLabel": "クロールタイプ", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.customEntryPointUrlsTextboxLabel": "カスタム入力ポイントURL", @@ -19456,7 +19486,6 @@ "xpack.ml.aiops.explainLogRateSpikes.docTitle": "ログレートスパイクを説明", "xpack.ml.aiopsBreadcrumbLabel": "AIOps", "xpack.ml.aiopsBreadcrumbs.explainLogRateSpikesLabel": "ログレートスパイクを説明", - "xpack.ml.aiopsBreadcrumbs.selectDateViewLabel": "データビュー", "xpack.ml.alertConditionValidation.title": "アラート条件には次の問題が含まれます。", "xpack.ml.alertContext.anomalyExplorerUrlDescription": "異常エクスプローラーを開くURL", "xpack.ml.alertContext.isInterimDescription": "上位の一致に中間結果が含まれるかどうかを示します", @@ -31013,7 +31042,6 @@ "xpack.synthetics.monitorManagement.closeButtonLabel": "閉じる", "xpack.synthetics.monitorManagement.closeLabel": "閉じる", "xpack.synthetics.monitorManagement.completed": "完了", - "xpack.synthetics.monitorManagement.confirmDescriptionLabel": "このアクションにより、モニターが削除されますが、収集されたデータはすべて保持されます。この操作は元に戻すことができません。", "xpack.synthetics.monitorManagement.createAgentPolicy": "エージェントポリシーを作成", "xpack.synthetics.monitorManagement.createMonitorLabel": "監視の作成", "xpack.synthetics.monitorManagement.delete": "場所を削除", @@ -31074,12 +31102,10 @@ "xpack.synthetics.monitorManagement.monitorAdvancedOptions.namespaceHelpLearnMoreLabel": "詳細情報", "xpack.synthetics.monitorManagement.monitorDeleteFailureMessage": "モニターを削除できませんでした。しばらくたってから再試行してください。", "xpack.synthetics.monitorManagement.monitorDeleteLoadingMessage": "モニターを削除しています...", - "xpack.synthetics.monitorManagement.monitorDeleteSuccessMessage": "モニターが正常に削除されました。", "xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "モニターは正常に更新されました。", "xpack.synthetics.monitorManagement.monitorFailureMessage": "モニターを保存できませんでした。しばらくたってから再試行してください。", "xpack.synthetics.monitorManagement.monitorList.actions": "アクション", "xpack.synthetics.monitorManagement.monitorList.enabled": "有効", - "xpack.synthetics.monitorManagement.monitorList.enabled.tooltip": "この監視は外部プロジェクトから追加されました。モニターを削除するには、プロジェクトから削除し、もう一度構成をプッシュします。", "xpack.synthetics.monitorManagement.monitorList.locations": "場所", "xpack.synthetics.monitorManagement.monitorList.monitorName": "モニター名", "xpack.synthetics.monitorManagement.monitorList.monitorType": "モニタータイプ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 10f0157999908f..ba247f6b94206e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6791,9 +6791,6 @@ "xpack.apm.compositeSpanCallsLabel": ",{count} 个调用,平均 {duration}", "xpack.apm.correlations.ccsWarningCalloutBody": "无法完全检索相关性分析的数据。仅 {version} 及更高版本支持此功能。", "xpack.apm.correlations.failedTransactions.helpPopover.basicExplanation": "相关性将帮助您发现哪些属性在区分事务失败与成功时具有最大影响。如果事务的 {field} 值为 {value},则认为其失败。", - "xpack.apm.correlations.fieldContextPopover.addFilterAriaLabel": "筛留 {fieldName}:“{value}”", - "xpack.apm.correlations.fieldContextPopover.calculatedFromSampleDescription": "基于 {sampleSize} 文档样例计算", - "xpack.apm.correlations.fieldContextPopover.removeFilterAriaLabel": "筛除 {fieldName}:“{value}”", "xpack.apm.correlations.progressTitle": "进度:{progress}%", "xpack.apm.durationDistribution.chart.percentileMarkerLabel": "第 {markerPercentile} 个百分位数", "xpack.apm.durationDistributionChart.totalSpansCount": "共 {totalDocCount} 个{totalDocCount, plural, other {跨度}}", @@ -7097,7 +7094,6 @@ "xpack.apm.correlations.failedTransactions.panelTitle": "失败事务延迟分布", "xpack.apm.correlations.failedTransactions.tableTitle": "相关性", "xpack.apm.correlations.fieldContextPopover.descriptionTooltipContent": "显示排名前 10 字段值", - "xpack.apm.correlations.fieldContextPopover.fieldTopValuesLabel": "排名前 10 值", "xpack.apm.correlations.fieldContextPopover.notTopTenValueMessage": "选定的词未排名前 10", "xpack.apm.correlations.fieldContextPopover.topFieldValuesAriaLabel": "显示排名前 10 字段值", "xpack.apm.correlations.highImpactText": "高", @@ -9939,7 +9935,6 @@ "xpack.csp.rules.ruleFlyout.remediationTabLabel": "补救", "xpack.csp.rules.rulesPageHeader.benchmarkIntegrationsButtonLabel": "基准集成", "xpack.csp.rules.rulesTable.cisSectionColumnLabel": "CIS 部分", - "xpack.csp.rules.rulesTable.lastModifiedColumnLabel": "最后修改时间", "xpack.csp.rules.rulesTable.nameColumnLabel": "名称", "xpack.csp.rules.rulesTable.searchPlaceholder": "搜索", "xpack.dataVisualizer.choroplethMap.topValuesCount": "{fieldName} 的排名最前值计数", @@ -9950,8 +9945,6 @@ "xpack.dataVisualizer.dataGrid.field.metricDistributionChart.tooltipValueBetweenLabel": "{percent}% 的文档具有介于 {minValFormatted} 和 {maxValFormatted} 之间的值", "xpack.dataVisualizer.dataGrid.field.metricDistributionChart.tooltipValueEqualLabel": "{percent}% 的文档的值为 {valFormatted}", "xpack.dataVisualizer.dataGrid.field.removeFilterAriaLabel": "筛除 {fieldName}:“{value}”", - "xpack.dataVisualizer.dataGrid.field.topValues.calculatedFromSampleDescription": "基于每个分片的 {topValuesSamplerShardSize} 文档样例计算", - "xpack.dataVisualizer.dataGrid.fieldExpandedRow.choroplethMapTopValues.calculatedFromSampleDescription": "基于每个分片的 {topValuesSamplerShardSize} 文档样例计算", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.numberContent.displayingPercentilesLabel": "正在显示 {minPercent} - {maxPercent} 百分位数", "xpack.dataVisualizer.dataGrid.fieldText.fieldMayBePopulatedDescription": "例如,可以使用文档映射中的 {copyToParam} 参数进行填充,也可以在索引后通过使用 {includesParam} 和 {excludesParam} 参数从 {sourceParam} 字段中修剪。", "xpack.dataVisualizer.dataGrid.fieldText.fieldNotPresentDescription": "查询的文档的 {sourceParam} 字段中不存在此字段。", @@ -9989,7 +9982,6 @@ "xpack.dataVisualizer.nameCollisionMsg": "“{name}”已存在,请提供唯一名称", "xpack.dataVisualizer.randomSamplerSettingsPopUp.probabilityLabel": "使用的概率:{samplingProbability}%", "xpack.dataVisualizer.searchPanel.ofFieldsTotal": ",共 {totalCount} 个", - "xpack.dataVisualizer.searchPanel.sampleSizeOptionLabel": "样本大小(每分片):{wrappedValue}", "xpack.dataVisualizer.searchPanel.totalDocCountLabel": "文档总数:{prepend}{strongTotalCount}", "xpack.dataVisualizer.searchPanel.totalDocCountNumber": "{totalCount, plural, other {#}}", "xpack.dataVisualizer.addCombinedFieldsLabel": "添加组合字段", @@ -10022,8 +10014,6 @@ "xpack.dataVisualizer.dataGrid.field.loadingLabel": "正在加载", "xpack.dataVisualizer.dataGrid.field.metricDistributionChart.seriesName": "分布", "xpack.dataVisualizer.dataGrid.field.topValuesLabel": "排名最前值", - "xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.falseCountLabel": "false", - "xpack.dataVisualizer.dataGrid.fieldExpandedRow.booleanContent.trueCountLabel": "true", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.countLabel": "计数", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.distinctValueLabel": "不同值", "xpack.dataVisualizer.dataGrid.fieldExpandedRow.documentStatsTable.metaTableTitle": "文档统计", @@ -10268,13 +10258,10 @@ "xpack.dataVisualizer.removeCombinedFieldsLabel": "移除组合字段", "xpack.dataVisualizer.samplingOptionsButton": "采样选项", "xpack.dataVisualizer.searchPanel.allFieldsLabel": "所有字段", - "xpack.dataVisualizer.searchPanel.allOptionLabel": "搜索全部", "xpack.dataVisualizer.searchPanel.invalidSyntax": "语法无效", "xpack.dataVisualizer.searchPanel.numberFieldsLabel": "字段数目", - "xpack.dataVisualizer.searchPanel.queryBarPlaceholder": "选择较小的样例大小将减少查询运行时间和集群上的负载。", "xpack.dataVisualizer.searchPanel.queryBarPlaceholderText": "搜索……(例如,status:200 AND extension:\"PHP\")", "xpack.dataVisualizer.searchPanel.randomSamplerMessage": "总文档计数和图表中将显示近似值,它们使用随机采样器聚合。", - "xpack.dataVisualizer.searchPanel.sampleSizeAriaLabel": "选择要采样的文档数目", "xpack.dataVisualizer.searchPanel.showEmptyFields": "显示空字段", "xpack.dataVisualizer.title": "上传文件", "xpack.embeddableEnhanced.actions.panelNotifications.manyDrilldowns": "面板有 {count} 个向下钻取", @@ -10365,13 +10352,59 @@ "xpack.enterpriseSearch.content.shared.result.header.metadata.icon.ariaLabel": "以下文档的元数据:{id}", "xpack.enterpriseSearch.crawler.action.deleteDomain.confirmationPopupMessage": "确定要移除域“{domainUrl}”和其所有设置?", "xpack.enterpriseSearch.crawler.addDomainForm.entryPointLabel": "网络爬虫入口点已设置为 {entryPointValue}", - "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.formDescription": "设置自动爬网。{readMoreMessage}。", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlCountOnDomains": "在 {domainCount, plural, other {# 个域}}上进行 {crawlType} 爬网", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.includeSitemapsCheckboxLabel": "包括在 {robotsDotTxt} 中发现的站点地图", "xpack.enterpriseSearch.crawler.crawlRulesTable.description": "创建爬网规则以包括或排除 URL 匹配规则的页面。规则按顺序运行,每个 URL 根据第一个匹配进行评估。{link}", "xpack.enterpriseSearch.crawler.deduplicationPanel.description": "网络爬虫仅索引唯一的页面。选择网络爬虫在考虑哪些网页重复时应使用的字段。取消选择所有架构字段以在此域上允许重复的文档。{documentationLink}。", "xpack.enterpriseSearch.crawler.deleteDomainModal.description": "从网络爬虫中移除域 {domainUrl}。这还会删除您已设置的所有入口点和爬网规则。将在下次爬网时移除与此域相关的任何文档。{thisCannotBeUndoneMessage}", "xpack.enterpriseSearch.crawler.entryPointsTable.emptyMessageDescription": "{link}以指定网络爬虫的入口点", + "xpack.enterpriseSearch.cronEditor.cronDaily.fieldHour.textAtLabel": "于", + "xpack.enterpriseSearch.cronEditor.cronDaily.fieldTimeLabel": "时间", + "xpack.enterpriseSearch.cronEditor.cronDaily.hourSelectLabel": "小时", + "xpack.enterpriseSearch.cronEditor.cronDaily.minuteSelectLabel": "分钟", + "xpack.enterpriseSearch.cronEditor.cronHourly.fieldMinute.textAtLabel": "@ 符号", + "xpack.enterpriseSearch.cronEditor.cronHourly.fieldTimeLabel": "分钟", + "xpack.enterpriseSearch.cronEditor.cronMonthly.fieldDateLabel": "日期", + "xpack.enterpriseSearch.cronEditor.cronMonthly.fieldHour.textAtLabel": "@ 符号", + "xpack.enterpriseSearch.cronEditor.cronMonthly.fieldTimeLabel": "时间", + "xpack.enterpriseSearch.cronEditor.cronMonthly.hourSelectLabel": "小时", + "xpack.enterpriseSearch.cronEditor.cronMonthly.minuteSelectLabel": "分钟", + "xpack.enterpriseSearch.cronEditor.cronMonthly.textOnTheLabel": "在", + "xpack.enterpriseSearch.cronEditor.cronWeekly.fieldDateLabel": "天", + "xpack.enterpriseSearch.cronEditor.cronWeekly.fieldHour.textAtLabel": "@ 符号", + "xpack.enterpriseSearch.cronEditor.cronWeekly.fieldTimeLabel": "时间", + "xpack.enterpriseSearch.cronEditor.cronWeekly.hourSelectLabel": "小时", + "xpack.enterpriseSearch.cronEditor.cronWeekly.minuteSelectLabel": "分钟", + "xpack.enterpriseSearch.cronEditor.cronWeekly.textOnLabel": "开启", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldDate.textOnTheLabel": "在", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldDateLabel": "日期", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldHour.textAtLabel": "@ 符号", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldMonth.textInLabel": "传入", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldMonthLabel": "月", + "xpack.enterpriseSearch.cronEditor.cronYearly.fieldTimeLabel": "时间", + "xpack.enterpriseSearch.cronEditor.cronYearly.hourSelectLabel": "小时", + "xpack.enterpriseSearch.cronEditor.cronYearly.minuteSelectLabel": "分钟", + "xpack.enterpriseSearch.cronEditor.day.friday": "星期五", + "xpack.enterpriseSearch.cronEditor.day.monday": "星期一", + "xpack.enterpriseSearch.cronEditor.day.saturday": "星期六", + "xpack.enterpriseSearch.cronEditor.day.sunday": "星期日", + "xpack.enterpriseSearch.cronEditor.day.thursday": "星期四", + "xpack.enterpriseSearch.cronEditor.day.tuesday": "星期二", + "xpack.enterpriseSearch.cronEditor.day.wednesday": "星期三", + "xpack.enterpriseSearch.cronEditor.fieldFrequencyLabel": "频率", + "xpack.enterpriseSearch.cronEditor.month.april": "四月", + "xpack.enterpriseSearch.cronEditor.month.august": "八月", + "xpack.enterpriseSearch.cronEditor.month.december": "十二月", + "xpack.enterpriseSearch.cronEditor.month.february": "二月", + "xpack.enterpriseSearch.cronEditor.month.january": "一月", + "xpack.enterpriseSearch.cronEditor.month.july": "七月", + "xpack.enterpriseSearch.cronEditor.month.june": "六月", + "xpack.enterpriseSearch.cronEditor.month.march": "三月", + "xpack.enterpriseSearch.cronEditor.month.may": "五月", + "xpack.enterpriseSearch.cronEditor.month.november": "十一月", + "xpack.enterpriseSearch.cronEditor.month.october": "十月", + "xpack.enterpriseSearch.cronEditor.month.september": "九月", + "xpack.enterpriseSearch.cronEditor.textEveryLabel": "每", "xpack.enterpriseSearch.errorConnectingState.cloudErrorMessage": "您的云部署是否正在运行 Enterprise Search 节点?{deploymentSettingsLink}", "xpack.enterpriseSearch.errorConnectingState.description1": "由于以下错误,我们无法与主机 URL {enterpriseSearchUrl} 的 Enterprise Search 建立连接:", "xpack.enterpriseSearch.errorConnectingState.description2": "确保在 {configFile} 中已正确配置主机 URL。", @@ -11403,21 +11436,21 @@ "xpack.enterpriseSearch.content.newIndex.steps.configureIngestion.title": "配置采集设置", "xpack.enterpriseSearch.content.newIndex.steps.createIndex.crawler.title": "使用网络爬虫编制索引", "xpack.enterpriseSearch.content.newIndex.steps.createIndex.title": "创建 Elasticsearch 索引", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.chineseDropDownOptionLabel": "中文", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.danishDropDownOptionLabel": "丹麦语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.dutchDropDownOptionLabel": "荷兰语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.englishDropDownOptionLabel": "英语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.frenchDropDownOptionLabel": "法语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.germanDropDownOptionLabel": "德语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.italianDropDownOptionLabel": "意大利语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.japaneseDropDownOptionLabel": "日语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.koreanDropDownOptionLabel": "朝鲜语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.portugueseBrazilDropDownOptionLabel": "葡萄牙语(巴西)", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.portugueseDropDownOptionLabel": "葡萄牙语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.russianDropDownOptionLabel": "俄语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.spanishDropDownOptionLabel": "西班牙语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.thaiDropDownOptionLabel": "泰语", - "xpack.enterpriseSearch.content.newIndex.supportedLanguages.universalDropDownOptionLabel": "通用", + "xpack.enterpriseSearch.content.supportedLanguages.chineseLabel": "中文", + "xpack.enterpriseSearch.content.supportedLanguages.danishLabel": "丹麦语", + "xpack.enterpriseSearch.content.supportedLanguages.dutchLabel": "荷兰语", + "xpack.enterpriseSearch.content.supportedLanguages.englishLabel": "英语", + "xpack.enterpriseSearch.content.supportedLanguages.frenchLabel": "法语", + "xpack.enterpriseSearch.content.supportedLanguages.germanLabel": "德语", + "xpack.enterpriseSearch.content.supportedLanguages.italianLabel": "意大利语", + "xpack.enterpriseSearch.content.supportedLanguages.japaneseLabel": "日语", + "xpack.enterpriseSearch.content.supportedLanguages.koreanLabel": "朝鲜语", + "xpack.enterpriseSearch.content.supportedLanguages.portugueseBrazilLabel": "葡萄牙语(巴西)", + "xpack.enterpriseSearch.content.supportedLanguages.portugueseLabel": "葡萄牙语", + "xpack.enterpriseSearch.content.supportedLanguages.russianLabel": "俄语", + "xpack.enterpriseSearch.content.supportedLanguages.spanishLabel": "西班牙语", + "xpack.enterpriseSearch.content.supportedLanguages.thaiLabel": "泰语", + "xpack.enterpriseSearch.content.supportedLanguages.universalLabel": "通用", "xpack.enterpriseSearch.content.newIndex.types.api": "API 终端", "xpack.enterpriseSearch.content.newIndex.types.connector": "连接器", "xpack.enterpriseSearch.content.newIndex.types.crawler": "网络爬虫", @@ -11507,13 +11540,10 @@ "xpack.enterpriseSearch.crawler.addDomainForm.urlLabel": "域 URL", "xpack.enterpriseSearch.crawler.addDomainForm.validateButtonLabel": "验证域", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.crawlAutomaticallySwitchLabel": "自动爬网", - "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.crawlUnitsPrefix": "每", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.readMoreLink": "阅读更多内容。", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.scheduleDescription": "爬网计划将对此索引上的每个域执行全面爬网。", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.scheduleFrequencyLabel": "计划频率", "xpack.enterpriseSearch.crawler.automaticCrawlSchedule.scheduleUnitsLabel": "计划时间单位", - "xpack.enterpriseSearch.crawler.automaticCrawlScheduler.disableCrawlSchedule.successMessage": "自动爬网已禁用。", - "xpack.enterpriseSearch.crawler.automaticCrawlScheduler.submitCrawlSchedule.successMessage": "您的自动爬网计划已更新。", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlDepthLabel": "最大爬网深度", "xpack.enterpriseSearch.crawler.components.crawlDetailsSummary.crawlTypeLabel": "爬网类型", "xpack.enterpriseSearch.crawler.crawlCustomSettingsFlyout.customEntryPointUrlsTextboxLabel": "定制入口点 URL", @@ -19486,7 +19516,6 @@ "xpack.ml.aiops.explainLogRateSpikes.docTitle": "解释日志速率峰值", "xpack.ml.aiopsBreadcrumbLabel": "AIOps", "xpack.ml.aiopsBreadcrumbs.explainLogRateSpikesLabel": "解释日志速率峰值", - "xpack.ml.aiopsBreadcrumbs.selectDateViewLabel": "数据视图", "xpack.ml.alertConditionValidation.title": "告警条件包含以下问题:", "xpack.ml.alertContext.anomalyExplorerUrlDescription": "要在 Anomaly Explorer 中打开的 URL", "xpack.ml.alertContext.isInterimDescription": "表示排名靠前的命中是否包含中间结果", @@ -31048,7 +31077,6 @@ "xpack.synthetics.monitorManagement.closeButtonLabel": "关闭", "xpack.synthetics.monitorManagement.closeLabel": "关闭", "xpack.synthetics.monitorManagement.completed": "已完成", - "xpack.synthetics.monitorManagement.confirmDescriptionLabel": "此操作将删除监测,但会保留收集的任何数据。此操作无法撤消。", "xpack.synthetics.monitorManagement.createAgentPolicy": "创建代理策略", "xpack.synthetics.monitorManagement.createMonitorLabel": "创建监测", "xpack.synthetics.monitorManagement.delete": "删除位置", @@ -31109,12 +31137,10 @@ "xpack.synthetics.monitorManagement.monitorAdvancedOptions.namespaceHelpLearnMoreLabel": "了解详情", "xpack.synthetics.monitorManagement.monitorDeleteFailureMessage": "无法删除监测。请稍后重试。", "xpack.synthetics.monitorManagement.monitorDeleteLoadingMessage": "正在删除监测......", - "xpack.synthetics.monitorManagement.monitorDeleteSuccessMessage": "已成功删除监测。", "xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "已成功更新监测。", "xpack.synthetics.monitorManagement.monitorFailureMessage": "无法保存监测。请稍后重试。", "xpack.synthetics.monitorManagement.monitorList.actions": "操作", "xpack.synthetics.monitorManagement.monitorList.enabled": "已启用", - "xpack.synthetics.monitorManagement.monitorList.enabled.tooltip": "已从外部项目添加此监测。要删除监测,请将其从项目中移除,然后再次推送配置。", "xpack.synthetics.monitorManagement.monitorList.locations": "位置", "xpack.synthetics.monitorManagement.monitorList.monitorName": "监测名称", "xpack.synthetics.monitorManagement.monitorList.monitorType": "监测类型", diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts b/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts index dc260578641f8b..049900bd30f4db 100644 --- a/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts +++ b/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts @@ -40,7 +40,7 @@ const getMockRule = () => { error: null, }, monitoring: { - execution: { + run: { history: [ { success: true, diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx b/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx index ed2a1d7b17e14e..cde89f97e53da2 100644 --- a/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx +++ b/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx @@ -88,6 +88,7 @@ export const StorybookContextDecorator: React.FC ruleTagFilter: true, ruleStatusFilter: true, rulesDetailLogs: true, + ruleLastRunOutcome: true, }, }); return ( diff --git a/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts b/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts index 8c7e5a500bf0e3..6a0f9b1c8f1403 100644 --- a/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts +++ b/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts @@ -17,6 +17,7 @@ export const allowedExperimentalValues = Object.freeze({ ruleTagFilter: true, ruleStatusFilter: true, rulesDetailLogs: true, + ruleLastRunOutcome: true, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index d0c61c884e528c..b7bc34819836da 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -25,18 +25,14 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; +import { ruleDetailsRoute } from '@kbn/rule-data-utils'; import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; import { ActionTypeRegistryContract, AlertsTableConfigurationRegistryContract, RuleTypeRegistryContract, } from '../types'; -import { - Section, - routeToRuleDetails, - legacyRouteToRuleDetails, - routeToConnectors, -} from './constants'; +import { Section, legacyRouteToRuleDetails, routeToConnectors } from './constants'; import { setDataViewsService } from '../common/lib/data_apis'; import { KibanaContextProvider, useKibana } from '../common/lib/kibana'; @@ -113,7 +109,7 @@ export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) = component={suspendedComponentWithProps(TriggersActionsUIHome, 'xl')} /> { + return values.reduce>( + (prev: Record, status: string) => ({ + ...prev, + [status]: 0, + }), + {} + ); +}; + type UseLoadRuleAggregationsProps = Omit & { onError: (message: string) => void; }; @@ -21,6 +31,7 @@ export function useLoadRuleAggregations({ typesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, onError, @@ -28,15 +39,13 @@ export function useLoadRuleAggregations({ const { http } = useKibana().services; const [rulesStatusesTotal, setRulesStatusesTotal] = useState>( - RuleExecutionStatusValues.reduce>( - (prev: Record, status: string) => ({ - ...prev, - [status]: 0, - }), - {} - ) + initializeAggregationResult(RuleExecutionStatusValues) ); + const [rulesLastRunOutcomesTotal, setRulesLastRunOutcomesTotal] = useState< + Record + >(initializeAggregationResult(RuleLastRunOutcomeValues)); + const internalLoadRuleAggregations = useCallback(async () => { try { const rulesAggs = await loadRuleAggregationsWithKueryFilter({ @@ -45,12 +54,16 @@ export function useLoadRuleAggregations({ typesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, }); if (rulesAggs?.ruleExecutionStatus) { setRulesStatusesTotal(rulesAggs.ruleExecutionStatus); } + if (rulesAggs?.ruleLastRunOutcome) { + setRulesLastRunOutcomesTotal(rulesAggs.ruleLastRunOutcome); + } } catch (e) { onError( i18n.translate( @@ -67,18 +80,28 @@ export function useLoadRuleAggregations({ typesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, onError, setRulesStatusesTotal, + setRulesLastRunOutcomesTotal, ]); return useMemo( () => ({ loadRuleAggregations: internalLoadRuleAggregations, rulesStatusesTotal, + rulesLastRunOutcomesTotal, setRulesStatusesTotal, + setRulesLastRunOutcomesTotal, }), - [internalLoadRuleAggregations, rulesStatusesTotal, setRulesStatusesTotal] + [ + internalLoadRuleAggregations, + rulesStatusesTotal, + rulesLastRunOutcomesTotal, + setRulesStatusesTotal, + setRulesLastRunOutcomesTotal, + ] ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.ts index 072c539ae90f58..45ac0a1d3c3b51 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules.ts @@ -89,6 +89,7 @@ export function useLoadRules({ typesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, sort, @@ -120,6 +121,7 @@ export function useLoadRules({ typesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, sort, @@ -144,6 +146,7 @@ export function useLoadRules({ hasEmptyTypesFilter && isEmpty(actionTypesFilter) && isEmpty(ruleExecutionStatusesFilter) && + isEmpty(ruleLastRunOutcomesFilter) && isEmpty(ruleStatusesFilter) && isEmpty(tagsFilter) ); @@ -168,6 +171,7 @@ export function useLoadRules({ typesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, sort, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts index 9c123a822a541b..4b36a8e8aeca37 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts @@ -43,6 +43,11 @@ const expectedTransformResult = [ { description: 'The space ID of the rule.', name: 'rule.spaceId' }, { description: 'The tags of the rule.', name: 'rule.tags' }, { description: 'The type of rule.', name: 'rule.type' }, + { + description: + 'The URL to the Stack Management rule page that generated the alert. This will be an empty string if the server.publicBaseUrl is not configured.', + name: 'rule.url', + }, { description: 'The date the rule scheduled the action.', name: 'date' }, { description: 'The ID of the alert that scheduled actions for the rule.', name: 'alert.id' }, { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts index bfa28ca2a97a47..42ae5bedc07472 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts @@ -41,6 +41,7 @@ export enum AlertProvidedActionVariables { ruleSpaceId = 'rule.spaceId', ruleTags = 'rule.tags', ruleType = 'rule.type', + ruleUrl = 'rule.url', date = 'date', alertId = 'alert.id', alertActionGroup = 'alert.actionGroup', @@ -105,6 +106,14 @@ function getAlwaysProvidedActionVariables(): ActionVariable[] { }), }); + result.push({ + name: AlertProvidedActionVariables.ruleUrl, + description: i18n.translate('xpack.triggersActionsUI.actionVariables.ruleUrlLabel', { + defaultMessage: + 'The URL to the Stack Management rule page that generated the alert. This will be an empty string if the server.publicBaseUrl is not configured.', + }), + }); + result.push({ name: AlertProvidedActionVariables.date, description: i18n.translate('xpack.triggersActionsUI.actionVariables.dateLabel', { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts index 7f63fcbaa30495..cc236ef876ee6a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts @@ -15,6 +15,7 @@ export interface RuleTagsAggregations { export const rewriteBodyRes: RewriteRequestCase = ({ rule_execution_status: ruleExecutionStatus, + rule_last_run_outcome: ruleLastRunOutcome, rule_enabled_status: ruleEnabledStatus, rule_muted_status: ruleMutedStatus, rule_snoozed_status: ruleSnoozedStatus, @@ -26,6 +27,7 @@ export const rewriteBodyRes: RewriteRequestCase = ({ ruleEnabledStatus, ruleMutedStatus, ruleSnoozedStatus, + ruleLastRunOutcome, ruleTags, }); @@ -41,6 +43,7 @@ export interface LoadRuleAggregationsProps { typesFilter?: string[]; actionTypesFilter?: string[]; ruleExecutionStatusesFilter?: string[]; + ruleLastRunOutcomesFilter?: string[]; ruleStatusesFilter?: RuleStatus[]; tagsFilter?: string[]; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/clone.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/clone.test.ts new file mode 100644 index 00000000000000..0de3b56f8693de --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/clone.test.ts @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { httpServiceMock } from '@kbn/core/public/mocks'; +import { cloneRule } from './clone'; + +const http = httpServiceMock.createStartContract(); + +describe('cloneRule', () => { + const resolvedValue = { + id: '12/3', + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + consumer: 'alerts', + schedule: { interval: '1m' }, + tags: [], + name: 'test', + rule_type_id: '.index-threshold', + notify_when: 'onActionGroupChange', + actions: [ + { + group: 'threshold met', + id: '1', + params: { + level: 'info', + message: 'alert ', + }, + connector_type_id: '.server-log', + }, + ], + scheduled_task_id: '1', + execution_status: { status: 'pending', last_execution_date: '2021-04-01T21:33:13.250Z' }, + create_at: '2021-04-01T21:33:13.247Z', + updated_at: '2021-04-01T21:33:13.247Z', + }; + + beforeEach(() => { + jest.clearAllMocks(); + http.post.mockResolvedValueOnce(resolvedValue); + }); + + test('should call _clone rule API', async () => { + const result = await cloneRule({ http, ruleId: '12/3' }); + expect(result).toMatchInlineSnapshot(` + Object { + "actions": Array [ + Object { + "actionTypeId": ".server-log", + "group": "threshold met", + "id": "1", + "params": Object { + "level": "info", + "message": "alert ", + }, + }, + ], + "activeSnoozes": undefined, + "apiKeyOwner": undefined, + "consumer": "alerts", + "create_at": "2021-04-01T21:33:13.247Z", + "createdAt": undefined, + "createdBy": undefined, + "executionStatus": Object { + "lastDuration": undefined, + "lastExecutionDate": "2021-04-01T21:33:13.250Z", + "status": "pending", + }, + "id": "12/3", + "isSnoozedUntil": undefined, + "muteAll": undefined, + "mutedInstanceIds": undefined, + "name": "test", + "notifyWhen": "onActionGroupChange", + "params": Object { + "aggType": "count", + "groupBy": "all", + "index": Array [ + ".kibana", + ], + "termSize": 5, + "threshold": Array [ + 1000, + ], + "thresholdComparator": ">", + "timeField": "alert.executionStatus.lastExecutionDate", + "timeWindowSize": 5, + "timeWindowUnit": "m", + }, + "ruleTypeId": ".index-threshold", + "schedule": Object { + "interval": "1m", + }, + "scheduledTaskId": "1", + "snoozeSchedule": undefined, + "tags": Array [], + "updatedAt": "2021-04-01T21:33:13.247Z", + "updatedBy": undefined, + } + `); + expect(http.post.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "/internal/alerting/rule/12%2F3/_clone", + ], + ] + `); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/clone.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/clone.ts new file mode 100644 index 00000000000000..f6e5d85f8230ad --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/clone.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 { HttpSetup } from '@kbn/core/public'; +import { AsApiContract } from '@kbn/actions-plugin/common'; +import { Rule } from '../../../types'; +import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; +import { transformRule } from './common_transformations'; + +export async function cloneRule({ + http, + ruleId, +}: { + http: HttpSetup; + ruleId: string; +}): Promise { + const res = await http.post>( + `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(ruleId)}/_clone` + ); + return transformRule(res); +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts index b5afe76889a26c..9f514893d1836c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts @@ -6,7 +6,7 @@ */ import { RuleExecutionStatus } from '@kbn/alerting-plugin/common'; import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common'; -import { Rule, RuleAction, ResolvedRule } from '../../../types'; +import { Rule, RuleAction, ResolvedRule, RuleLastRun } from '../../../types'; const transformAction: RewriteRequestCase = ({ group, @@ -30,6 +30,16 @@ const transformExecutionStatus: RewriteRequestCase = ({ ...rest, }); +const transformLastRun: RewriteRequestCase = ({ + outcome_msg: outcomeMsg, + alerts_count: alertsCount, + ...rest +}) => ({ + outcomeMsg, + alertsCount, + ...rest, +}); + export const transformRule: RewriteRequestCase = ({ rule_type_id: ruleTypeId, created_by: createdBy, @@ -46,6 +56,8 @@ export const transformRule: RewriteRequestCase = ({ snooze_schedule: snoozeSchedule, is_snoozed_until: isSnoozedUntil, active_snoozes: activeSnoozes, + last_run: lastRun, + next_run: nextRun, ...rest }: any) => ({ ruleTypeId, @@ -65,6 +77,8 @@ export const transformRule: RewriteRequestCase = ({ scheduledTaskId, isSnoozedUntil, activeSnoozes, + ...(lastRun ? { lastRun: transformLastRun(lastRun) } : {}), + ...(nextRun ? { nextRun } : {}), ...rest, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts index 4e37f9a02fb808..c04b55ff6db499 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts @@ -12,7 +12,13 @@ import { transformRule } from './common_transformations'; type RuleCreateBody = Omit< RuleUpdates, - 'createdBy' | 'updatedBy' | 'muteAll' | 'mutedInstanceIds' | 'executionStatus' + | 'createdBy' + | 'updatedBy' + | 'muteAll' + | 'mutedInstanceIds' + | 'executionStatus' + | 'lastRun' + | 'nextRun' >; const rewriteBodyRequest: RewriteResponseCase = ({ ruleTypeId, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts index ff181a124d0fe0..09b053608542ab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/index.ts @@ -9,6 +9,7 @@ export { alertingFrameworkHealth } from './health'; export type { LoadRuleAggregationsProps } from './aggregate_helpers'; export { loadRuleAggregations, loadRuleTags } from './aggregate'; export { createRule } from './create'; +export { cloneRule } from './clone'; export { deleteRules } from './delete'; export { disableRule, disableRules } from './disable'; export { enableRule, enableRules } from './enable'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.test.ts index c3ec9070681da0..1426207b715e05 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.test.ts @@ -57,6 +57,30 @@ describe('mapFiltersToKueryNode', () => { ); }); + test('should handle ruleLastRunOutcomesFilter', () => { + expect( + toElasticsearchQuery( + mapFiltersToKueryNode({ + ruleLastRunOutcomesFilter: ['succeeded'], + }) as KueryNode + ) + ).toEqual( + toElasticsearchQuery(fromKueryExpression('alert.attributes.lastRun.outcome: succeeded')) + ); + + expect( + toElasticsearchQuery( + mapFiltersToKueryNode({ + ruleLastRunOutcomesFilter: ['succeeded', 'failed', 'warning'], + }) as KueryNode + ) + ).toEqual( + toElasticsearchQuery( + fromKueryExpression('alert.attributes.lastRun.outcome: (succeeded or failed or warning)') + ) + ); + }); + test('should handle ruleStatusesFilter', () => { expect( toElasticsearchQuery( @@ -260,6 +284,7 @@ describe('mapFiltersToKueryNode', () => { typesFilter: ['type', 'filter'], actionTypesFilter: ['action', 'types', 'filter'], ruleExecutionStatusesFilter: ['alert', 'statuses', 'filter'], + ruleLastRunOutcomesFilter: ['warning', 'failed'], tagsFilter: ['a', 'b', 'c'], }) as KueryNode ) @@ -271,6 +296,7 @@ describe('mapFiltersToKueryNode', () => { alert.attributes.actions:{ actionTypeId:types } OR alert.attributes.actions:{ actionTypeId:filter }) and alert.attributes.executionStatus.status:(alert or statuses or filter) and + alert.attributes.lastRun.outcome:(warning or failed) and alert.attributes.tags:(a or b or c)` ) ) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts index a49b0a489bfec7..8159501b3d4b4a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kuery_node.ts @@ -12,6 +12,7 @@ export const mapFiltersToKueryNode = ({ typesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, searchText, @@ -20,6 +21,7 @@ export const mapFiltersToKueryNode = ({ actionTypesFilter?: string[]; tagsFilter?: string[]; ruleExecutionStatusesFilter?: string[]; + ruleLastRunOutcomesFilter?: string[]; ruleStatusesFilter?: RuleStatus[]; searchText?: string; }): KueryNode | null => { @@ -51,6 +53,16 @@ export const mapFiltersToKueryNode = ({ ); } + if (ruleLastRunOutcomesFilter && ruleLastRunOutcomesFilter.length) { + filterKueryNode.push( + nodeBuilder.or( + ruleLastRunOutcomesFilter.map((resf) => + nodeBuilder.is('alert.attributes.lastRun.outcome', resf) + ) + ) + ); + } + if (ruleStatusesFilter && ruleStatusesFilter.length) { const snoozedFilter = nodeBuilder.or([ fromKueryExpression('alert.attributes.muteAll: true'), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts index dab59919092a2d..cce9fd3e5ac979 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts @@ -18,6 +18,7 @@ export interface LoadRulesProps { actionTypesFilter?: string[]; tagsFilter?: string[]; ruleExecutionStatusesFilter?: string[]; + ruleLastRunOutcomesFilter?: string[]; ruleStatusesFilter?: RuleStatus[]; sort?: Sorting; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts index 171b514429cd74..46d2c32ccdbddf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts @@ -18,6 +18,7 @@ export async function loadRulesWithKueryFilter({ typesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, sort = { field: 'name', direction: 'asc' }, @@ -32,6 +33,7 @@ export async function loadRulesWithKueryFilter({ actionTypesFilter, tagsFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, searchText, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx index fa93ae18ec701a..aac9176366042d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx @@ -56,6 +56,7 @@ import { LoadGlobalExecutionKPIAggregationsProps, bulkUnsnoozeRules, BulkUnsnoozeRulesProps, + cloneRule, } from '../../../lib/rule_api'; import { useKibana } from '../../../../common/lib/kibana'; @@ -101,6 +102,7 @@ export interface ComponentOpts { bulkSnoozeRules: (props: BulkSnoozeRulesProps) => Promise; unsnoozeRule: (rule: Rule, scheduleIds?: string[]) => Promise; bulkUnsnoozeRules: (props: BulkUnsnoozeRulesProps) => Promise; + cloneRule: (ruleId: string) => Promise; } export type PropsWithOptionalApiHandlers = Omit & Partial; @@ -221,6 +223,9 @@ export function withBulkRuleOperations( bulkUnsnoozeRules={async (bulkUnsnoozeRulesProps: BulkUnsnoozeRulesProps) => { return await bulkUnsnoozeRules({ http, ...bulkUnsnoozeRulesProps }); }} + cloneRule={async (ruleId: string) => { + return await cloneRule({ http, ruleId }); + }} /> ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx index 02c740d2b6cc8c..d7540150c90ba6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule.tsx @@ -8,11 +8,7 @@ import React, { lazy } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiTabbedContent } from '@elastic/eui'; -import { - ActionGroup, - RuleExecutionStatusErrorReasons, - AlertStatusValues, -} from '@kbn/alerting-plugin/common'; +import { ActionGroup, AlertStatusValues } from '@kbn/alerting-plugin/common'; import { useKibana } from '../../../../common/lib/kibana'; import { Rule, RuleSummary, AlertStatus, RuleType } from '../../../../types'; import { @@ -20,16 +16,20 @@ import { withBulkRuleOperations, } from '../../common/components/with_bulk_rule_api_operations'; import './rule.scss'; -import { getHealthColor } from '../../rules_list/components/rule_execution_status_filter'; -import { - rulesStatusesTranslationsMapping, - ALERT_STATUS_LICENSE_ERROR, -} from '../../rules_list/translations'; import type { RuleEventLogListProps } from './rule_event_log_list'; import { AlertListItem } from './types'; import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; import { suspendedComponentWithProps } from '../../../lib/suspended_component_with_props'; +import { + getRuleHealthColor, + getRuleStatusMessage, +} from '../../../../common/lib/rule_status_helpers'; import RuleStatusPanelWithApi from './rule_status_panel'; +import { + ALERT_STATUS_LICENSE_ERROR, + rulesLastRunOutcomeTranslationMapping, + rulesStatusesTranslationsMapping, +} from '../../rules_list/translations'; const RuleEventLogList = lazy(() => import('./rule_event_log_list')); const RuleAlertList = lazy(() => import('./rule_alert_list')); @@ -78,12 +78,13 @@ export function RuleComponent({ requestRefresh(); }; - const healthColor = getHealthColor(rule.executionStatus.status); - const isLicenseError = - rule.executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License; - const statusMessage = isLicenseError - ? ALERT_STATUS_LICENSE_ERROR - : rulesStatusesTranslationsMapping[rule.executionStatus.status]; + const healthColor = getRuleHealthColor(rule); + const statusMessage = getRuleStatusMessage({ + rule, + licenseErrorText: ALERT_STATUS_LICENSE_ERROR, + lastOutcomeTranslations: rulesLastRunOutcomeTranslationMapping, + executionStatusTranslations: rulesStatusesTranslationsMapping, + }); const renderRuleAlertList = () => { return suspendedComponentWithProps( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx index df0d35c5961f7f..ab99a727fdebf9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx @@ -25,6 +25,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { RuleExecutionStatusErrorReasons, parseDuration } from '@kbn/alerting-plugin/common'; +import { getRuleDetailsRoute } from '@kbn/rule-data-utils'; import { UpdateApiKeyModalConfirmation } from '../../../components/update_api_key_modal_confirmation'; import { bulkUpdateAPIKey, deleteRules } from '../../../lib/rule_api'; import { DeleteModalConfirmation } from '../../../components/delete_modal_confirmation'; @@ -50,7 +51,7 @@ import { import { RuleRouteWithApi } from './rule_route'; import { ViewInApp } from './view_in_app'; import { RuleEdit } from '../../rule_form'; -import { routeToRuleDetails, routeToRules } from '../../../constants'; +import { routeToRules } from '../../../constants'; import { rulesErrorReasonTranslationsMapping, rulesWarningReasonTranslationsMapping, @@ -209,7 +210,7 @@ export const RuleDetails: React.FunctionComponent = ({ }, [rule.schedule.interval, config.minimumScheduleInterval, toasts, hasEditButton]); const setRule = async () => { - history.push(routeToRuleDetails.replace(`:ruleId`, rule.id)); + history.push(getRuleDetailsRoute(rule.id)); }; const goToRulesList = () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_data_grid.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_data_grid.tsx index 20f3612f3a41be..c7b4c84f64c30d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_data_grid.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_data_grid.tsx @@ -30,6 +30,7 @@ import { executionLogSortableColumns, ExecutionLogSortFields, } from '@kbn/alerting-plugin/common'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; import { RuleEventLogListCellRenderer, ColumnId } from './rule_event_log_list_cell_renderer'; import { RuleEventLogPaginationStatus } from './rule_event_log_pagination_status'; import { RuleActionErrorBadge } from './rule_action_error_badge'; @@ -174,6 +175,8 @@ export const RuleEventLogDataGrid = (props: RuleEventLogDataGrid) => { const { euiTheme } = useEuiTheme(); + const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome'); + const getPaginatedRowIndex = useCallback( (rowIndex: number) => { const { pageIndex, pageSize } = pagination; @@ -621,6 +624,7 @@ export const RuleEventLogDataGrid = (props: RuleEventLogDataGrid) => { dateFormat={dateFormat} ruleId={ruleId} spaceIds={spaceIds} + lastRunOutcomeEnabled={isRuleLastRunOutcomeEnabled} />
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_cell_renderer.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_cell_renderer.tsx index bcca56ad0027ed..956021ddc6754e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_cell_renderer.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_cell_renderer.tsx @@ -10,7 +10,7 @@ import moment from 'moment'; import { EuiLink } from '@elastic/eui'; import { RuleAlertingOutcome } from '@kbn/alerting-plugin/common'; import { useHistory } from 'react-router-dom'; -import { routeToRuleDetails } from '../../../constants'; +import { getRuleDetailsRoute } from '@kbn/rule-data-utils'; import { formatRuleAlertCount } from '../../../../common/lib/format_rule_alert_count'; import { useKibana, useSpacesData } from '../../../../common/lib/kibana'; import { RuleEventLogListStatus } from './rule_event_log_list_status'; @@ -32,10 +32,19 @@ interface RuleEventLogListCellRendererProps { dateFormat?: string; ruleId?: string; spaceIds?: string[]; + lastRunOutcomeEnabled?: boolean; } export const RuleEventLogListCellRenderer = (props: RuleEventLogListCellRendererProps) => { - const { columnId, value, version, dateFormat = DEFAULT_DATE_FORMAT, ruleId, spaceIds } = props; + const { + columnId, + value, + version, + dateFormat = DEFAULT_DATE_FORMAT, + ruleId, + spaceIds, + lastRunOutcomeEnabled = false, + } = props; const spacesData = useSpacesData(); const { http } = useKibana().services; @@ -53,7 +62,9 @@ export const RuleEventLogListCellRenderer = (props: RuleEventLogListCellRenderer const ruleNamePathname = useMemo(() => { if (!ruleId) return ''; - const ruleRoute = routeToRuleDetails.replace(':ruleId', ruleId); + + const ruleRoute = getRuleDetailsRoute(ruleId); + if (ruleOnDifferentSpace) { const [linkedSpaceId] = spaceIds ?? []; const basePath = http.basePath.get(); @@ -85,7 +96,12 @@ export const RuleEventLogListCellRenderer = (props: RuleEventLogListCellRenderer } if (columnId === 'status') { - return ; + return ( + + ); } if (columnId === 'timestamp') { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx index c51109bd11514d..718222636830a9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.test.tsx @@ -11,6 +11,7 @@ import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { loadExecutionKPIAggregations } from '../../../lib/rule_api/load_execution_kpi_aggregations'; import { loadGlobalExecutionKPIAggregations } from '../../../lib/rule_api/load_global_execution_kpi_aggregations'; import { RuleEventLogListKPI } from './rule_event_log_list_kpi'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; jest.mock('../../../../common/lib/kibana', () => ({ useKibana: jest.fn().mockReturnValue({ @@ -28,6 +29,10 @@ jest.mock('../../../lib/rule_api/load_global_execution_kpi_aggregations', () => loadGlobalExecutionKPIAggregations: jest.fn(), })); +jest.mock('../../../../common/get_experimental_features', () => ({ + getIsExperimentalFeatureEnabled: jest.fn(), +})); + const mockKpiResponse = { success: 4, unknown: 0, @@ -48,6 +53,7 @@ const loadGlobalExecutionKPIAggregationsMock = describe('rule_event_log_list_kpi', () => { beforeEach(() => { jest.clearAllMocks(); + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); loadExecutionKPIAggregationsMock.mockResolvedValue(mockKpiResponse); loadGlobalExecutionKPIAggregationsMock.mockResolvedValue(mockKpiResponse); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx index 0696f857261ec7..783b985c973462 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_kpi.tsx @@ -14,6 +14,7 @@ import { ComponentOpts as RuleApis, withBulkRuleOperations, } from '../../common/components/with_bulk_rule_api_operations'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; import { useKibana } from '../../../../common/lib/kibana'; import { RuleEventLogListStatus } from './rule_event_log_list_status'; @@ -104,6 +105,7 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => { } = useKibana().services; const isInitialized = useRef(false); + const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome'); const [isLoading, setIsLoading] = useState(false); const [kpi, setKpi] = useState(); @@ -168,7 +170,12 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => { )} + description={getStatDescription( + + )} titleSize="s" title={kpi?.success ?? 0} isLoading={isLoadingData} @@ -177,7 +184,12 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => { )} + description={getStatDescription( + + )} titleSize="s" title={kpi?.warning ?? 0} isLoading={isLoadingData} @@ -186,7 +198,12 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => { )} + description={getStatDescription( + + )} titleSize="s" title={kpi?.failure ?? 0} isLoading={isLoadingData} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status.tsx index d82915f2fd59d0..9640b6704ef801 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status.tsx @@ -5,12 +5,19 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiIcon } from '@elastic/eui'; import { RuleAlertingOutcome } from '@kbn/alerting-plugin/common'; +import { + RULE_LAST_RUN_OUTCOME_SUCCEEDED, + RULE_LAST_RUN_OUTCOME_FAILED, + RULE_LAST_RUN_OUTCOME_WARNING, + ALERT_STATUS_UNKNOWN, +} from '../../rules_list/translations'; interface RuleEventLogListStatusProps { status: RuleAlertingOutcome; + lastRunOutcomeEnabled?: boolean; } const statusContainerStyles = { @@ -30,14 +37,28 @@ const STATUS_TO_COLOR: Record = { warning: 'warning', }; +const STATUS_TO_OUTCOME: Record = { + success: RULE_LAST_RUN_OUTCOME_SUCCEEDED, + failure: RULE_LAST_RUN_OUTCOME_FAILED, + warning: RULE_LAST_RUN_OUTCOME_WARNING, + unknown: ALERT_STATUS_UNKNOWN, +}; + export const RuleEventLogListStatus = (props: RuleEventLogListStatusProps) => { - const { status } = props; + const { status, lastRunOutcomeEnabled = false } = props; const color = STATUS_TO_COLOR[status] || 'gray'; + const statusString = useMemo(() => { + if (lastRunOutcomeEnabled) { + return STATUS_TO_OUTCOME[status].toLocaleLowerCase(); + } + return status; + }, [lastRunOutcomeEnabled, status]); + return (
- {status} + {statusString}
); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status_filter.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status_filter.test.tsx index 14591c7a15ccd6..c4bbc7e7205001 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status_filter.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status_filter.test.tsx @@ -9,6 +9,15 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui'; import { RuleEventLogListStatusFilter } from './rule_event_log_list_status_filter'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; + +jest.mock('../../../../common/get_experimental_features', () => ({ + getIsExperimentalFeatureEnabled: jest.fn(), +})); + +beforeEach(() => { + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); +}); const onChangeMock = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status_filter.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status_filter.tsx index 6d1b38a7ae94de..af4325c906456e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status_filter.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list_status_filter.tsx @@ -9,6 +9,7 @@ import React, { useState, useCallback } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { RuleAlertingOutcome } from '@kbn/alerting-plugin/common'; import { EuiFilterButton, EuiPopover, EuiFilterGroup, EuiFilterSelectItem } from '@elastic/eui'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; import { RuleEventLogListStatus } from './rule_event_log_list_status'; const statusFilters: RuleAlertingOutcome[] = ['success', 'failure', 'warning', 'unknown']; @@ -21,6 +22,8 @@ interface RuleEventLogListStatusFilterProps { export const RuleEventLogListStatusFilter = (props: RuleEventLogListStatusFilterProps) => { const { selectedOptions = [], onChange = () => {} } = props; + const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome'); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); const onFilterItemClick = useCallback( @@ -68,7 +71,10 @@ export const RuleEventLogListStatusFilter = (props: RuleEventLogListStatusFilter onClick={onFilterItemClick(status)} checked={selectedOptions.includes(status) ? 'on' : undefined} > - + ); })} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_status_panel.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_status_panel.tsx index 6a6563877b76f4..370cc6953d26c2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_status_panel.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_status_panel.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import datemath from '@kbn/datemath'; -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import moment from 'moment'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -32,7 +32,7 @@ export interface RuleStatusPanelProps { isEditable: boolean; requestRefresh: () => void; healthColor: string; - statusMessage: string; + statusMessage?: string | null; } type ComponentOpts = Pick< @@ -68,6 +68,20 @@ export const RuleStatusPanel: React.FC = ({ [rule, unsnoozeRule] ); + const statusMessageDisplay = useMemo(() => { + if (!statusMessage) { + return ( + + ); + } + return statusMessage; + }, [rule, statusMessage]); + const getLastNumberOfExecutions = useCallback(async () => { try { const result = await loadExecutionLogAggregations({ @@ -142,7 +156,7 @@ export const RuleStatusPanel: React.FC = ({ color={healthColor} style={{ fontWeight: 400 }} > - {statusMessage} + {statusMessageDisplay} } description={i18n.translate( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index ed2b6c27a06048..d37e41ff265551 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -169,6 +169,7 @@ export const RuleForm = ({ ruleTypes, error: loadRuleTypesError, ruleTypeIndex, + ruleTypesIsLoading, } = useLoadRuleTypes({ filteredRuleTypes: ruleTypeToFilter }); // load rule types @@ -848,7 +849,7 @@ export const RuleForm = ({ ) : null} {ruleTypeNodes} - ) : ruleTypeIndex ? ( + ) : ruleTypeIndex && !ruleTypesIsLoading ? ( ) : ( @@ -871,7 +872,7 @@ const NoAuthorizedRuleTypes = ({ operation }: { operation: string }) => (

diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.test.tsx index bba71fd6ec55fb..b3c09ded1bbfa0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.test.tsx @@ -24,6 +24,7 @@ const snoozeRule = jest.fn(); const unsnoozeRule = jest.fn(); const onLoading = jest.fn(); const onRunRule = jest.fn(); +const onCloneRule = jest.fn(); export const tick = (ms = 0) => new Promise((resolve) => { @@ -97,269 +98,308 @@ describe('CollapsedItemActions', () => { unsnoozeRule, onLoading, onRunRule, + onCloneRule, }; }; - test('renders panel items as disabled', async () => { - await setup(); - const wrapper = mountWithIntl( - - ); - await act(async () => { - await nextTick(); - wrapper.update(); + describe('with app context', () => { + beforeAll(async () => { + await setup(false); }); - expect( - wrapper.find('[data-test-subj="selectActionButton"]').first().props().disabled - ).toBeTruthy(); - }); - test('renders closed popover initially and opens on click with all actions enabled', async () => { - await setup(); - const wrapper = mountWithIntl(); - - expect(wrapper.find('[data-test-subj="selectActionButton"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="collapsedActionPanel"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="snoozeButton"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="disableButton"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="editRule"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="deleteRule"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="updateApiKey"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="runRule"]').exists()).toBeFalsy(); - - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + afterAll(() => { + jest.clearAllMocks(); }); - expect(wrapper.find('[data-test-subj="collapsedActionPanel"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="snoozeButton"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="disableButton"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="editRule"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="deleteRule"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="updateApiKey"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="runRule"]').exists()).toBeTruthy(); - - expect( - wrapper.find('[data-test-subj="selectActionButton"]').first().props().disabled - ).toBeFalsy(); - - expect(wrapper.find(`[data-test-subj="disableButton"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="disableButton"] button`).text()).toEqual('Disable'); - expect(wrapper.find(`[data-test-subj="snoozeButton"] button`).text()).toEqual('Snooze'); - expect(wrapper.find(`[data-test-subj="editRule"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="editRule"] button`).text()).toEqual('Edit rule'); - expect(wrapper.find(`[data-test-subj="deleteRule"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="deleteRule"] button`).text()).toEqual('Delete rule'); - expect(wrapper.find(`[data-test-subj="updateApiKey"] button`).text()).toEqual('Update API key'); - expect(wrapper.find(`[data-test-subj="runRule"] button`).text()).toEqual('Run rule'); - }); + test('renders actions correctly when rule type is not editable in this context', async () => { + const wrapper = mountWithIntl(); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); - test('handles case when run rule is clicked', async () => { - await setup(); - const wrapper = mountWithIntl(); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + expect(wrapper.find(`[data-test-subj="disableButton"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="disableButton"] button`).text()).toEqual('Disable'); + expect(wrapper.find(`[data-test-subj="editRule"] button`).prop('disabled')).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="editRule"] button`).text()).toEqual('Edit rule'); + expect(wrapper.find(`[data-test-subj="deleteRule"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="deleteRule"] button`).text()).toEqual('Delete rule'); }); - wrapper.find('button[data-test-subj="runRule"]').simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); - }); - expect(onRunRule).toHaveBeenCalled(); }); - test('handles case when rule is unmuted and enabled and disable is clicked', async () => { - await setup(); - const wrapper = mountWithIntl(); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + describe('without app context', () => { + beforeAll(async () => { + await setup(); }); - wrapper.find('button[data-test-subj="disableButton"]').simulate('click'); - await act(async () => { - await tick(10); - wrapper.update(); + + afterAll(() => { + jest.clearAllMocks(); }); - expect(disableRule).toHaveBeenCalled(); - }); - test('handles case when rule is unmuted and disabled and enable is clicked', async () => { - await setup(); - const wrapper = mountWithIntl( - - ); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + afterEach(() => { + jest.useRealTimers(); }); - wrapper.find('button[data-test-subj="disableButton"]').simulate('click'); - await act(async () => { - await tick(10); - wrapper.update(); + + test('renders panel items as disabled', async () => { + const wrapper = mountWithIntl( + + ); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect( + wrapper.find('[data-test-subj="selectActionButton"]').first().props().disabled + ).toBeTruthy(); }); - expect(enableRule).toHaveBeenCalled(); - }); - test('handles case when edit rule is clicked', async () => { - await setup(); - const wrapper = mountWithIntl(); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + test('renders closed popover initially and opens on click with all actions enabled', async () => { + const wrapper = mountWithIntl(); + + expect(wrapper.find('[data-test-subj="selectActionButton"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="collapsedActionPanel"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="snoozeButton"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="disableButton"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="editRule"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="deleteRule"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="updateApiKey"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="runRule"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="cloneRule"]').exists()).toBeFalsy(); + + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="collapsedActionPanel"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="snoozeButton"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="disableButton"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="editRule"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="deleteRule"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="updateApiKey"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="runRule"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="cloneRule"]').exists()).toBeTruthy(); + + expect( + wrapper.find('[data-test-subj="selectActionButton"]').first().props().disabled + ).toBeFalsy(); + + expect(wrapper.find(`[data-test-subj="disableButton"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="disableButton"] button`).text()).toEqual('Disable'); + expect(wrapper.find(`[data-test-subj="snoozeButton"] button`).text()).toEqual('Snooze'); + expect(wrapper.find(`[data-test-subj="editRule"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="editRule"] button`).text()).toEqual('Edit rule'); + expect(wrapper.find(`[data-test-subj="deleteRule"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="deleteRule"] button`).text()).toEqual('Delete rule'); + expect(wrapper.find(`[data-test-subj="updateApiKey"] button`).text()).toEqual( + 'Update API key' + ); + expect(wrapper.find(`[data-test-subj="runRule"] button`).text()).toEqual('Run rule'); + expect(wrapper.find('[data-test-subj="cloneRule"] button').text()).toEqual('Clone rule'); }); - wrapper.find('button[data-test-subj="editRule"]').simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + + test('handles case when run rule is clicked', async () => { + const wrapper = mountWithIntl(); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + wrapper.find('button[data-test-subj="runRule"]').simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(onRunRule).toHaveBeenCalled(); }); - expect(onEditRule).toHaveBeenCalled(); - }); - test('handles case when delete rule is clicked', async () => { - await setup(); - const wrapper = mountWithIntl(); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + test('handles case when rule is unmuted and enabled and disable is clicked', async () => { + const wrapper = mountWithIntl(); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + wrapper.find('button[data-test-subj="disableButton"]').simulate('click'); + await act(async () => { + await tick(10); + wrapper.update(); + }); + expect(disableRule).toHaveBeenCalled(); }); - wrapper.find('button[data-test-subj="deleteRule"]').simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + + test('handles case when rule is unmuted and disabled and enable is clicked', async () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + wrapper.find('button[data-test-subj="disableButton"]').simulate('click'); + await act(async () => { + await tick(10); + wrapper.update(); + }); + expect(enableRule).toHaveBeenCalled(); }); - expect(setRulesToDelete).toHaveBeenCalled(); - }); - test('renders actions correctly when rule is disabled', async () => { - await setup(); - const wrapper = mountWithIntl( - - ); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + test('handles case when edit rule is clicked', async () => { + const wrapper = mountWithIntl(); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + wrapper.find('button[data-test-subj="editRule"]').simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(onEditRule).toHaveBeenCalled(); }); - expect(wrapper.find(`[data-test-subj="snoozeButton"] button`).exists()).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="disableButton"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="disableButton"] button`).text()).toEqual('Enable'); - expect(wrapper.find(`[data-test-subj="editRule"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="editRule"] button`).text()).toEqual('Edit rule'); - expect(wrapper.find(`[data-test-subj="deleteRule"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="deleteRule"] button`).text()).toEqual('Delete rule'); - }); + test('handles case when delete rule is clicked', async () => { + const wrapper = mountWithIntl(); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + wrapper.find('button[data-test-subj="deleteRule"]').simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(setRulesToDelete).toHaveBeenCalled(); + }); - test('renders actions correctly when rule is not editable', async () => { - await setup(); - const wrapper = mountWithIntl( - - ); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + test('renders actions correctly when rule is disabled', async () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(`[data-test-subj="snoozeButton"] button`).exists()).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="disableButton"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="disableButton"] button`).text()).toEqual('Enable'); + expect(wrapper.find(`[data-test-subj="editRule"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="editRule"] button`).text()).toEqual('Edit rule'); + expect(wrapper.find(`[data-test-subj="deleteRule"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="deleteRule"] button`).text()).toEqual('Delete rule'); }); - expect( - wrapper.find(`[data-test-subj="selectActionButton"] button`).prop('disabled') - ).toBeTruthy(); - }); + test('renders actions correctly when rule is not editable', async () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); - test('renders actions correctly when rule is not enabled due to license', async () => { - await setup(); - const wrapper = mountWithIntl( - - ); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + expect( + wrapper.find(`[data-test-subj="selectActionButton"] button`).prop('disabled') + ).toBeTruthy(); }); - expect(wrapper.find(`[data-test-subj="snoozeButton"] button`).prop('disabled')).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="disableButton"] button`).prop('disabled')).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="disableButton"] button`).text()).toEqual('Disable'); - expect(wrapper.find(`[data-test-subj="editRule"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="editRule"] button`).text()).toEqual('Edit rule'); - expect(wrapper.find(`[data-test-subj="deleteRule"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="deleteRule"] button`).text()).toEqual('Delete rule'); - }); + test('renders actions correctly when rule is not enabled due to license', async () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); - test('renders actions correctly when rule is muted', async () => { - await setup(); - const wrapper = mountWithIntl( - - ); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + expect(wrapper.find(`[data-test-subj="snoozeButton"] button`).prop('disabled')).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="disableButton"] button`).prop('disabled')).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="disableButton"] button`).text()).toEqual('Disable'); + expect(wrapper.find(`[data-test-subj="editRule"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="editRule"] button`).text()).toEqual('Edit rule'); + expect(wrapper.find(`[data-test-subj="deleteRule"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="deleteRule"] button`).text()).toEqual('Delete rule'); }); - expect(wrapper.find('[data-test-subj="snoozeButton"] button').text()).toEqual( - 'Snoozed indefinitely' - ); - expect(wrapper.find(`[data-test-subj="disableButton"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="disableButton"] button`).text()).toEqual('Disable'); - expect(wrapper.find(`[data-test-subj="editRule"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="editRule"] button`).text()).toEqual('Edit rule'); - expect(wrapper.find(`[data-test-subj="deleteRule"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="deleteRule"] button`).text()).toEqual('Delete rule'); - }); + test('renders actions correctly when rule is muted', async () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); - test('renders actions correctly when rule type is not editable in this context', async () => { - await setup(false); - const wrapper = mountWithIntl(); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - await nextTick(); - wrapper.update(); + expect(wrapper.find('[data-test-subj="snoozeButton"] button').text()).toEqual( + 'Snoozed indefinitely' + ); + expect(wrapper.find(`[data-test-subj="disableButton"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="disableButton"] button`).text()).toEqual('Disable'); + expect(wrapper.find(`[data-test-subj="editRule"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="editRule"] button`).text()).toEqual('Edit rule'); + expect(wrapper.find(`[data-test-subj="deleteRule"] button`).prop('disabled')).toBeFalsy(); + expect(wrapper.find(`[data-test-subj="deleteRule"] button`).text()).toEqual('Delete rule'); }); - expect(wrapper.find(`[data-test-subj="disableButton"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="disableButton"] button`).text()).toEqual('Disable'); - expect(wrapper.find(`[data-test-subj="editRule"] button`).prop('disabled')).toBeTruthy(); - expect(wrapper.find(`[data-test-subj="editRule"] button`).text()).toEqual('Edit rule'); - expect(wrapper.find(`[data-test-subj="deleteRule"] button`).prop('disabled')).toBeFalsy(); - expect(wrapper.find(`[data-test-subj="deleteRule"] button`).text()).toEqual('Delete rule'); - }); + test('renders snooze text correctly if the rule is snoozed', async () => { + jest.useFakeTimers('modern').setSystemTime(moment('1990-01-01').toDate()); + const wrapper = mountWithIntl( + + ); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + jest.runOnlyPendingTimers(); + }); + expect(wrapper.find('[data-test-subj="snoozeButton"] button').text()).toEqual( + 'Snoozed until Feb 1' + ); + }); - test('renders snooze text correctly if the rule is snoozed', async () => { - jest.useFakeTimers('modern').setSystemTime(moment('1990-01-01').toDate()); - await setup(); - const wrapper = mountWithIntl( - - ); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - await act(async () => { - jest.runOnlyPendingTimers(); + test('snooze is disabled for SIEM rules', async () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + expect(wrapper.find('[data-test-subj="snoozeButton"]').exists()).toBeFalsy(); }); - expect(wrapper.find('[data-test-subj="snoozeButton"] button').text()).toEqual( - 'Snoozed until Feb 1' - ); - }); - test('snooze is disabled for SIEM rules', async () => { - await setup(); - const wrapper = mountWithIntl( - - ); - wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); - expect(wrapper.find('[data-test-subj="snoozeButton"]').exists()).toBeFalsy(); + test('clone rule is disabled for SIEM rules', async () => { + const wrapper = mountWithIntl( + + ); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + expect(wrapper.find(`[data-test-subj="cloneRule"] button`).prop('disabled')).toBeTruthy(); + }); + + test('handles case when clone rule is clicked', async () => { + const wrapper = mountWithIntl(); + wrapper.find('[data-test-subj="selectActionButton"]').first().simulate('click'); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + wrapper.find('button[data-test-subj="cloneRule"]').simulate('click'); + expect(onCloneRule).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx index 54957724a186d4..37a1af034a7b76 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx @@ -43,6 +43,7 @@ export type ComponentOpts = { onEditRule: (item: RuleTableItem) => void; onUpdateAPIKey: (id: string[]) => void; onRunRule: (item: RuleTableItem) => void; + onCloneRule: (ruleId: string) => void; } & Pick; export const CollapsedItemActions: React.FunctionComponent = ({ @@ -57,6 +58,7 @@ export const CollapsedItemActions: React.FunctionComponent = ({ snoozeRule, unsnoozeRule, onRunRule, + onCloneRule, }: ComponentOpts) => { const { ruleTypeRegistry, @@ -209,6 +211,18 @@ export const CollapsedItemActions: React.FunctionComponent = ({ { defaultMessage: 'Disable' } ), }, + { + disabled: !item.isEditable || item.consumer === AlertConsumers.SIEM, + 'data-test-subj': 'cloneRule', + onClick: async () => { + setIsPopoverOpen(!isPopoverOpen); + onCloneRule(item.id); + }, + name: i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.collapsedItemActons.cloneRuleTitle', + { defaultMessage: 'Clone rule' } + ), + }, { disabled: !item.isEditable || !isRuleTypeEditableInContext, 'data-test-subj': 'editRule', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_execution_status_filter.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_execution_status_filter.tsx index e5bb7ffd1b0e42..76451421c9c8e8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_execution_status_filter.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_execution_status_filter.tsx @@ -10,6 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiPopover, EuiFilterButton, EuiFilterSelectItem, EuiHealth } from '@elastic/eui'; import { RuleExecutionStatuses, RuleExecutionStatusValues } from '@kbn/alerting-plugin/common'; import { rulesStatusesTranslationsMapping } from '../translations'; +import { getExecutionStatusHealthColor } from '../../../../common/lib'; interface RuleExecutionStatusFilterProps { selectedStatuses: string[]; @@ -66,7 +67,7 @@ export const RuleExecutionStatusFilter: React.FunctionComponent
{sortedRuleExecutionStatusValues.map((item: RuleExecutionStatuses) => { - const healthColor = getHealthColor(item); + const healthColor = getExecutionStatusHealthColor(item); return ( void; +} + +export const RuleLastRunOutcomeFilter: React.FunctionComponent = ({ + selectedOutcomes, + onChange, +}: RuleLastRunOutcomeFilterProps) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const onTogglePopover = useCallback(() => { + setIsPopoverOpen((prevIsPopoverOpen) => !prevIsPopoverOpen); + }, [setIsPopoverOpen]); + + const onClosePopover = useCallback(() => { + setIsPopoverOpen(false); + }, [setIsPopoverOpen]); + + const onFilterSelectItem = useCallback( + (filterItem: string) => () => { + const isPreviouslyChecked = selectedOutcomes.includes(filterItem); + if (isPreviouslyChecked) { + onChange?.(selectedOutcomes.filter((val) => val !== filterItem)); + } else { + onChange?.(selectedOutcomes.concat(filterItem)); + } + }, + [onChange, selectedOutcomes] + ); + + return ( + 0} + numActiveFilters={selectedOutcomes.length} + numFilters={selectedOutcomes.length} + onClick={onTogglePopover} + data-test-subj="ruleLastRunOutcomeFilterButton" + > + + + } + > +
+ {sortedRuleLastRunOutcomeValues.map((item: RuleLastRunOutcomes) => { + const healthColor = getOutcomeHealthColor(item); + return ( + + + {rulesLastRunOutcomeTranslationMapping[item]} + + + ); + })} +
+
+ ); +}; + +export { getOutcomeHealthColor as getHealthColor }; 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 938349702f90c4..ef44ec07656f7c 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 @@ -358,6 +358,14 @@ describe('rules_list component with props', () => { }); describe('Last response filter', () => { + beforeEach(() => { + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => true); + }); + + afterEach(() => { + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); + }); + let wrapper: ReactWrapper; async function setup(editable: boolean = true) { loadRulesWithKueryFilter.mockResolvedValue({ @@ -408,7 +416,7 @@ describe('rules_list component with props', () => { // eslint-disable-next-line react-hooks/rules-of-hooks useKibanaMock().services.actionTypeRegistry = actionTypeRegistry; wrapper = mountWithIntl( - + ); await act(async () => { await nextTick(); @@ -420,49 +428,48 @@ describe('rules_list component with props', () => { expect(loadRuleAggregationsWithKueryFilter).toHaveBeenCalled(); } it('can filter by last response', async () => { - (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => true); loadRulesWithKueryFilter.mockReset(); await setup(); expect(loadRulesWithKueryFilter).toHaveBeenLastCalledWith( expect.objectContaining({ - ruleExecutionStatusesFilter: ['error'], + ruleLastRunOutcomesFilter: ['failed'], }) ); - wrapper.find('[data-test-subj="ruleExecutionStatusFilterButton"] button').simulate('click'); + wrapper.find('[data-test-subj="ruleLastRunOutcomeFilterButton"] button').simulate('click'); wrapper - .find('[data-test-subj="ruleExecutionStatusactiveFilterOption"]') + .find('[data-test-subj="ruleLastRunOutcomesucceededFilterOption"]') .first() .simulate('click'); expect(loadRulesWithKueryFilter).toHaveBeenLastCalledWith( expect.objectContaining({ - ruleExecutionStatusesFilter: ['error', 'active'], + ruleLastRunOutcomesFilter: ['failed', 'succeeded'], }) ); - expect(wrapper.prop('onLastResponseFilterChange')).toHaveBeenCalled(); - expect(wrapper.prop('onLastResponseFilterChange')).toHaveBeenLastCalledWith([ - 'error', - 'active', + expect(wrapper.prop('onLastRunOutcomeFilterChange')).toHaveBeenCalled(); + expect(wrapper.prop('onLastRunOutcomeFilterChange')).toHaveBeenLastCalledWith([ + 'failed', + 'succeeded', ]); - wrapper.find('[data-test-subj="ruleExecutionStatusFilterButton"] button').simulate('click'); + wrapper.find('[data-test-subj="ruleLastRunOutcomeFilterButton"] button').simulate('click'); wrapper - .find('[data-test-subj="ruleExecutionStatuserrorFilterOption"]') + .find('[data-test-subj="ruleLastRunOutcomefailedFilterOption"]') .first() .simulate('click'); expect(loadRulesWithKueryFilter).toHaveBeenLastCalledWith( expect.objectContaining({ - ruleExecutionStatusesFilter: ['active'], + ruleLastRunOutcomesFilter: ['succeeded'], }) ); - expect(wrapper.prop('onLastResponseFilterChange')).toHaveBeenCalled(); - expect(wrapper.prop('onLastResponseFilterChange')).toHaveBeenLastCalledWith(['active']); + expect(wrapper.prop('onLastRunOutcomeFilterChange')).toHaveBeenCalled(); + expect(wrapper.prop('onLastRunOutcomeFilterChange')).toHaveBeenLastCalledWith(['succeeded']); }); }); @@ -844,6 +851,11 @@ describe('rules_list component with items', () => { loadRuleTypes.mockResolvedValue([ruleTypeFromApi]); loadAllActions.mockResolvedValue([]); loadRuleAggregationsWithKueryFilter.mockResolvedValue({ + ruleLastRunOutcome: { + succeeded: 3, + failed: 3, + warning: 6, + }, ruleEnabledStatus: { enabled: 2, disabled: 0 }, ruleExecutionStatus: { ok: 1, active: 2, error: 3, pending: 4, unknown: 5, warning: 6 }, ruleMutedStatus: { muted: 0, unmuted: 2 }, @@ -1005,11 +1017,9 @@ describe('rules_list component with items', () => { expect( wrapper.find('EuiTableRowCell[data-test-subj="rulesTableCell-lastResponse"]').length ).toEqual(mockedRulesData.length); - expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-active"]').length).toEqual(1); - expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-ok"]').length).toEqual(1); - expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-pending"]').length).toEqual(1); - expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-unknown"]').length).toEqual(0); - expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-error"]').length).toEqual(2); + + expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-succeeded"]').length).toEqual(2); + expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-failed"]').length).toEqual(2); expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-warning"]').length).toEqual(1); expect(wrapper.find('[data-test-subj="ruleStatus-error-tooltip"]').length).toEqual(2); expect( @@ -1018,10 +1028,10 @@ describe('rules_list component with items', () => { expect(wrapper.find('[data-test-subj="rulesListAutoRefresh"]').exists()).toBeTruthy(); - expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-error"]').first().text()).toEqual( + expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-failed"]').first().text()).toEqual( 'Error' ); - expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-error"]').last().text()).toEqual( + expect(wrapper.find('EuiHealth[data-test-subj="ruleStatus-failed"]').last().text()).toEqual( 'License Error' ); }); @@ -1042,7 +1052,7 @@ describe('rules_list component with items', () => { mockedRulesData.forEach((rule, index) => { if (rule.monitoring) { expect(ratios.at(index).text()).toEqual( - `${rule.monitoring.execution.calculated_metrics.success_ratio * 100}%` + `${rule.monitoring.run.calculated_metrics.success_ratio * 100}%` ); } else { expect(ratios.at(index).text()).toEqual(`N/A`); @@ -1060,10 +1070,10 @@ describe('rules_list component with items', () => { ); mockedRulesData.forEach((rule, index) => { - if (typeof rule.monitoring?.execution.calculated_metrics.p50 === 'number') { + if (typeof rule.monitoring?.run.calculated_metrics.p50 === 'number') { // Ensure the table cells are getting the correct values expect(percentiles.at(index).text()).toEqual( - getFormattedDuration(rule.monitoring.execution.calculated_metrics.p50) + getFormattedDuration(rule.monitoring.run.calculated_metrics.p50) ); // Ensure the tooltip is showing the correct content expect( @@ -1073,7 +1083,7 @@ describe('rules_list component with items', () => { ) .at(index) .props().content - ).toEqual(getFormattedMilliseconds(rule.monitoring.execution.calculated_metrics.p50)); + ).toEqual(getFormattedMilliseconds(rule.monitoring.run.calculated_metrics.p50)); } else { expect(percentiles.at(index).text()).toEqual('N/A'); } @@ -1149,9 +1159,9 @@ describe('rules_list component with items', () => { ); mockedRulesData.forEach((rule, index) => { - if (typeof rule.monitoring?.execution.calculated_metrics.p95 === 'number') { + if (typeof rule.monitoring?.run.calculated_metrics.p95 === 'number') { expect(percentiles.at(index).text()).toEqual( - getFormattedDuration(rule.monitoring.execution.calculated_metrics.p95) + getFormattedDuration(rule.monitoring.run.calculated_metrics.p95) ); } else { expect(percentiles.at(index).text()).toEqual('N/A'); @@ -1270,21 +1280,19 @@ describe('rules_list component with items', () => { }); it('renders brief', async () => { + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => true); await setup(); - // { ok: 1, active: 2, error: 3, pending: 4, unknown: 5, warning: 6 } - expect(wrapper.find('EuiHealth[data-test-subj="totalOkRulesCount"]').text()).toEqual('Ok: 1'); - expect(wrapper.find('EuiHealth[data-test-subj="totalActiveRulesCount"]').text()).toEqual( - 'Active: 2' - ); - expect(wrapper.find('EuiHealth[data-test-subj="totalErrorRulesCount"]').text()).toEqual( - 'Error: 3' - ); - expect(wrapper.find('EuiHealth[data-test-subj="totalPendingRulesCount"]').text()).toEqual( - 'Pending: 4' + // ruleLastRunOutcome: { + // succeeded: 3, + // failed: 3, + // warning: 6, + // } + expect(wrapper.find('EuiHealth[data-test-subj="totalSucceededRulesCount"]').text()).toEqual( + 'Succeeded: 3' ); - expect(wrapper.find('EuiHealth[data-test-subj="totalUnknownRulesCount"]').text()).toEqual( - 'Unknown: 5' + expect(wrapper.find('EuiHealth[data-test-subj="totalFailedRulesCount"]').text()).toEqual( + 'Failed: 3' ); expect(wrapper.find('EuiHealth[data-test-subj="totalWarningRulesCount"]').text()).toEqual( 'Warning: 6' diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index ed5bac05c4edef..a180610784b040 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -12,7 +12,7 @@ import moment from 'moment'; import { capitalize, isEmpty, sortBy } from 'lodash'; import { KueryNode } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useEffect, useState, ReactNode, useCallback, useMemo } from 'react'; +import React, { useEffect, useState, ReactNode, useCallback, useMemo, useRef } from 'react'; import { EuiButton, EuiFieldSearch, @@ -22,13 +22,10 @@ import { EuiSpacer, EuiLink, EuiEmptyPrompt, - EuiHealth, EuiTableSortingType, EuiButtonIcon, EuiSelectableOption, - EuiIcon, EuiDescriptionList, - EuiCallOut, } from '@elastic/eui'; import { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components/selectable/selectable_option'; import { useHistory } from 'react-router-dom'; @@ -37,8 +34,10 @@ import { RuleExecutionStatus, ALERTS_FEATURE_ID, RuleExecutionStatusErrorReasons, + RuleLastRunOutcomeValues, } from '@kbn/alerting-plugin/common'; import { AlertingConnectorFeatureId } from '@kbn/actions-plugin/common'; +import { ruleDetailsRoute as commonRuleDetailsRoute } from '@kbn/rule-data-utils'; import { ActionType, Rule, @@ -55,9 +54,12 @@ import { RuleAdd, RuleEdit } from '../../rule_form'; import { BulkOperationPopover } from '../../common/components/bulk_operation_popover'; import { RuleQuickEditButtonsWithApi as RuleQuickEditButtons } from '../../common/components/rule_quick_edit_buttons'; import { CollapsedItemActionsWithApi as CollapsedItemActions } from './collapsed_item_actions'; +import { RulesListStatuses } from './rules_list_statuses'; import { TypeFilter } from './type_filter'; import { ActionTypeFilter } from './action_type_filter'; import { RuleExecutionStatusFilter } from './rule_execution_status_filter'; +import { RuleLastRunOutcomeFilter } from './rule_last_run_outcome_filter'; +import { RulesListErrorBanner } from './rules_list_error_banner'; import { loadRuleTypes, disableRule, @@ -65,10 +67,11 @@ import { snoozeRule, unsnoozeRule, bulkUpdateAPIKey, + cloneRule, } from '../../../lib/rule_api'; import { loadActionTypes } from '../../../lib/action_connector_api'; import { hasAllPrivilege, hasExecuteActionsCapability } from '../../../lib/capabilities'; -import { routeToRuleDetails, DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants'; +import { DEFAULT_SEARCH_PAGE_SIZE } from '../../../constants'; import { RulesDeleteModalConfirmation } from '../../../components/rules_delete_modal_confirmation'; import { EmptyPrompt } from '../../../components/prompts/empty_prompt'; import { ALERT_STATUS_LICENSE_ERROR } from '../translations'; @@ -116,6 +119,8 @@ export interface RulesListProps { onStatusFilterChange?: (status: RuleStatus[]) => RulesPageContainerState; lastResponseFilter?: string[]; onLastResponseFilterChange?: (lastResponse: string[]) => RulesPageContainerState; + lastRunOutcomeFilter?: string[]; + onLastRunOutcomeFilterChange?: (lastRunOutcome: string[]) => RulesPageContainerState; refresh?: Date; rulesListKey?: string; visibleColumns?: RulesListVisibleColumns[]; @@ -128,9 +133,9 @@ interface RuleTypeState { } export const percentileFields = { - [Percentiles.P50]: 'monitoring.execution.calculated_metrics.p50', - [Percentiles.P95]: 'monitoring.execution.calculated_metrics.p95', - [Percentiles.P99]: 'monitoring.execution.calculated_metrics.p99', + [Percentiles.P50]: 'monitoring.run.calculated_metrics.p50', + [Percentiles.P95]: 'monitoring.run.calculated_metrics.p95', + [Percentiles.P99]: 'monitoring.run.calculated_metrics.p99', }; const initialPercentileOptions = Object.values(Percentiles).map((percentile) => ({ @@ -148,6 +153,8 @@ export const RulesList = ({ onStatusFilterChange, lastResponseFilter, onLastResponseFilterChange, + lastRunOutcomeFilter, + onLastRunOutcomeFilterChange, refresh, rulesListKey, visibleColumns, @@ -174,6 +181,9 @@ export const RulesList = ({ const [ruleExecutionStatusesFilter, setRuleExecutionStatusesFilter] = useState( lastResponseFilter || [] ); + const [ruleLastRunOutcomesFilter, setRuleLastRunOutcomesFilter] = useState( + lastRunOutcomeFilter || [] + ); const [ruleStatusesFilter, setRuleStatusesFilter] = useState(statusFilter || []); const [tagsFilter, setTagsFilter] = useState([]); @@ -188,6 +198,9 @@ export const RulesList = ({ const isRuleTagFilterEnabled = getIsExperimentalFeatureEnabled('ruleTagFilter'); const isRuleStatusFilterEnabled = getIsExperimentalFeatureEnabled('ruleStatusFilter'); + const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome'); + + const cloneRuleId = useRef(null); useEffect(() => { (async () => { @@ -247,6 +260,7 @@ export const RulesList = ({ const [isUnsnoozingRules, setIsUnsnoozingRules] = useState(false); const [isUnschedulingRules, setIsUnschedulingRules] = useState(false); const [isUpdatingRuleAPIKeys, setIsUpdatingRuleAPIKeys] = useState(false); + const [isCloningRule, setIsCloningRule] = useState(false); const hasAnyAuthorizedRuleType = useMemo(() => { return ruleTypesState.isInitialized && ruleTypesState.data.size > 0; @@ -277,6 +291,7 @@ export const RulesList = ({ typesFilter: rulesTypesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, sort, @@ -289,15 +304,17 @@ export const RulesList = ({ onError, }); - const { loadRuleAggregations, rulesStatusesTotal } = useLoadRuleAggregations({ - searchText, - typesFilter, - actionTypesFilter, - ruleExecutionStatusesFilter, - ruleStatusesFilter, - tagsFilter, - onError, - }); + const { loadRuleAggregations, rulesStatusesTotal, rulesLastRunOutcomesTotal } = + useLoadRuleAggregations({ + searchText, + typesFilter: rulesTypesFilter, + actionTypesFilter, + ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, + ruleStatusesFilter, + tagsFilter, + onError, + }); const onRuleEdit = (ruleItem: RuleTableItem) => { setEditFlyoutVisibility(true); @@ -331,6 +348,18 @@ export const RulesList = ({ ruleTypesState, ]); + const tableItems = useMemo(() => { + if (ruleTypesState.isInitialized === false) { + return []; + } + return convertRulesToTableItems({ + rules: rulesState.data, + ruleTypeIndex: ruleTypesState.data, + canExecuteActions, + config, + }); + }, [ruleTypesState, rulesState.data, canExecuteActions, config]); + useEffect(() => { refreshRules(); }, [refreshRules, refresh, percentileOptions]); @@ -402,12 +431,24 @@ export const RulesList = ({ } }, [lastResponseFilter]); + useEffect(() => { + if (lastRunOutcomeFilter) { + setRuleLastRunOutcomesFilter(lastRunOutcomeFilter); + } + }, [lastResponseFilter]); + useEffect(() => { if (onLastResponseFilterChange) { onLastResponseFilterChange(ruleExecutionStatusesFilter); } }, [ruleExecutionStatusesFilter]); + useEffect(() => { + if (onLastRunOutcomeFilterChange) { + onLastRunOutcomeFilterChange(ruleLastRunOutcomesFilter); + } + }, [ruleLastRunOutcomesFilter]); + // Clear bulk selection anytime the filters change useEffect(() => { onClearSelection(); @@ -416,11 +457,23 @@ export const RulesList = ({ rulesTypesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, hasDefaultRuleTypesFiltersOn, ]); + useEffect(() => { + if (cloneRuleId.current) { + const ruleItem = tableItems.find((ti) => ti.id === cloneRuleId.current); + cloneRuleId.current = null; + setIsCloningRule(false); + if (ruleItem) { + onRuleEdit(ruleItem); + } + } + }, [tableItems]); + const buildErrorListItems = (_executionStatus: RuleExecutionStatus) => { const hasErrorMessage = _executionStatus.status === 'error'; const errorMessage = _executionStatus?.error?.message; @@ -465,7 +518,11 @@ export const RulesList = ({ setShowErrors((prevValue) => { if (!prevValue) { const rulesToExpand = rulesState.data.reduce((acc, ruleItem) => { - if (ruleItem.executionStatus.status === 'error') { + // Check both outcome and executionStatus for now until we deprecate executionStatus + if ( + ruleItem.lastRun?.outcome === RuleLastRunOutcomeValues[2] || + ruleItem.executionStatus.status === 'error' + ) { return { ...acc, [ruleItem.id]: ( @@ -528,6 +585,25 @@ export const RulesList = ({ return null; }; + const getRuleOutcomeOrStatusFilter = () => { + if (isRuleLastRunOutcomeEnabled) { + return [ + , + ]; + } + return [ + , + ]; + }; + const onDisableRule = (rule: RuleTableItem) => { return disableRule({ http, id: rule.id }); }; @@ -571,26 +647,10 @@ export const RulesList = ({ filters={typesFilter} /> ), - , + ...getRuleOutcomeOrStatusFilter(), ...getRuleTagFilter(), ]; - const tableItems = useMemo(() => { - if (ruleTypesState.isInitialized === false) { - return []; - } - return convertRulesToTableItems({ - rules: rulesState.data, - ruleTypeIndex: ruleTypesState.data, - canExecuteActions, - config, - }); - }, [ruleTypesState, rulesState, canExecuteActions, config]); - const { isAllSelected, selectedIds, @@ -609,6 +669,7 @@ export const RulesList = ({ typesFilter: rulesTypesFilter, actionTypesFilter, ruleExecutionStatusesFilter, + ruleLastRunOutcomesFilter, ruleStatusesFilter, tagsFilter, }); @@ -665,7 +726,8 @@ export const RulesList = ({ isUnsnoozingRules || isSchedulingRules || isUnschedulingRules || - isUpdatingRuleAPIKeys + isUpdatingRuleAPIKeys || + isCloningRule ); }, [ rulesState, @@ -677,38 +739,33 @@ export const RulesList = ({ isSchedulingRules, isUnschedulingRules, isUpdatingRuleAPIKeys, + isCloningRule, ]); + const onCloneRule = async (ruleId: string) => { + setIsCloningRule(true); + try { + const RuleCloned = await cloneRule({ http, ruleId }); + cloneRuleId.current = RuleCloned.id; + await loadRules(); + } catch { + cloneRuleId.current = null; + setIsCloningRule(false); + toasts.addDanger( + i18n.translate('xpack.triggersActionsUI.sections.rulesList.cloneFailed', { + defaultMessage: 'Unable to clone rule', + }) + ); + } + }; + const table = ( <> - {rulesStatusesTotal.error > 0 ? ( - <> - -

- -   - -   - setRuleExecutionStatusesFilter(['error'])}> - - -

-
- - - ) : null} + {authorizedToCreateAnyRules && showCreateRuleButton ? ( @@ -778,68 +835,10 @@ export const RulesList = ({ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -886,7 +885,7 @@ export const RulesList = ({ onPage={setPage} onRuleChanged={() => refreshRules()} onRuleClick={(rule) => { - const detailsRoute = ruleDetailsRoute ? ruleDetailsRoute : routeToRuleDetails; + const detailsRoute = ruleDetailsRoute ? ruleDetailsRoute : commonRuleDetailsRoute; history.push(detailsRoute.replace(`:ruleId`, rule.id)); }} onRuleEditClick={(rule) => { @@ -920,6 +919,7 @@ export const RulesList = ({ onEditRule={() => onRuleEdit(rule)} onUpdateAPIKey={setRulesToUpdateAPIKey} onRunRule={() => onRunRule(rule.id)} + onCloneRule={onCloneRule} /> )} renderRuleError={(rule) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_column_selector.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_column_selector.tsx index c88070cceafee3..39089c2168cb35 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_column_selector.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_column_selector.tsx @@ -36,7 +36,8 @@ export type RulesListVisibleColumns = | 'ruleExecutionPercentile' | 'ruleExecutionSuccessRatio' | 'ruleExecutionStatus' - | 'ruleExecutionState'; + | 'ruleExecutionState' + | 'ruleLastRunOutcome'; const OriginalRulesListVisibleColumns: RulesListVisibleColumns[] = [ 'ruleName', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_error_banner.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_error_banner.tsx new file mode 100644 index 00000000000000..dd8a99b4d3a600 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_error_banner.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 from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiCallOut, EuiIcon, EuiLink, EuiSpacer } from '@elastic/eui'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; + +interface RulesListErrorBannerProps { + rulesLastRunOutcomes: Record; + setRuleExecutionStatusesFilter: (statuses: string[]) => void; + setRuleLastRunOutcomesFilter: (outcomes: string[]) => void; +} + +export const RulesListErrorBanner = (props: RulesListErrorBannerProps) => { + const { rulesLastRunOutcomes, setRuleExecutionStatusesFilter, setRuleLastRunOutcomesFilter } = + props; + + const onClick = () => { + const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome'); + if (isRuleLastRunOutcomeEnabled) { + setRuleLastRunOutcomesFilter(['failed']); + } else { + setRuleExecutionStatusesFilter(['error']); + } + }; + + if (rulesLastRunOutcomes.failed === 0) { + return null; + } + + return ( + <> + +

+ +   + +   + + + +

+
+ + + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_statuses.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_statuses.tsx new file mode 100644 index 00000000000000..7e01311b71c1df --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_statuses.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiHealth } from '@elastic/eui'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; + +import { + RULE_STATUS_ACTIVE, + RULE_STATUS_ERROR, + RULE_STATUS_WARNING, + RULE_STATUS_OK, + RULE_STATUS_PENDING, + RULE_STATUS_UNKNOWN, + RULE_LAST_RUN_OUTCOME_SUCCEEDED_DESCRIPTION, + RULE_LAST_RUN_OUTCOME_WARNING_DESCRIPTION, + RULE_LAST_RUN_OUTCOME_FAILED_DESCRIPTION, +} from '../translations'; + +interface RulesListStatusesProps { + rulesStatuses: Record; + rulesLastRunOutcomes: Record; +} + +export const RulesListStatuses = (props: RulesListStatusesProps) => { + const { rulesStatuses, rulesLastRunOutcomes } = props; + + const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome'); + + if (isRuleLastRunOutcomeEnabled) { + return ( + + + + {RULE_LAST_RUN_OUTCOME_SUCCEEDED_DESCRIPTION(rulesLastRunOutcomes.succeeded)} + + + + + {RULE_LAST_RUN_OUTCOME_FAILED_DESCRIPTION(rulesLastRunOutcomes.failed)} + + + + + {RULE_LAST_RUN_OUTCOME_WARNING_DESCRIPTION(rulesLastRunOutcomes.warning)} + + + + ); + } + + return ( + + + + {RULE_STATUS_ACTIVE(rulesStatuses.active)} + + + + + {RULE_STATUS_ERROR(rulesStatuses.error)} + + + + + {RULE_STATUS_WARNING(rulesStatuses.warning)} + + + + + {RULE_STATUS_OK(rulesStatuses.ok)} + + + + + {RULE_STATUS_PENDING(rulesStatuses.pending)} + + + + + {RULE_STATUS_UNKNOWN(rulesStatuses.unknown)} + + + + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx index f171f36ebb8dd0..e888875e77115e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx @@ -9,7 +9,6 @@ import moment from 'moment'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import { AlertConsumers } from '@kbn/rule-data-utils'; -import { FormattedMessage } from '@kbn/i18n-react'; import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; import { EuiBasicTable, @@ -18,7 +17,6 @@ import { EuiIconTip, EuiLink, EuiButtonEmpty, - EuiHealth, EuiText, EuiToolTip, EuiTableSortingType, @@ -32,21 +30,17 @@ import { } from '@elastic/eui'; import { RuleExecutionStatus, - RuleExecutionStatusErrorReasons, formatDuration, parseDuration, MONITORING_HISTORY_LIMIT, } from '@kbn/alerting-plugin/common'; import { - rulesStatusesTranslationsMapping, - ALERT_STATUS_LICENSE_ERROR, SELECT_ALL_RULES, CLEAR_SELECTION, TOTAL_RULES, SELECT_ALL_ARIA_LABEL, } from '../translations'; -import { getHealthColor } from './rule_execution_status_filter'; import { Rule, RuleTableItem, @@ -67,6 +61,8 @@ import { hasAllPrivilege } from '../../../lib/capabilities'; import { RuleTagBadge } from './rule_tag_badge'; import { RuleStatusDropdown } from './rule_status_dropdown'; import { RulesListNotifyBadge } from './rules_list_notify_badge'; +import { RulesListTableStatusCell } from './rules_list_table_status_cell'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; import { RulesListColumns, RulesListVisibleColumns, @@ -92,9 +88,9 @@ const percentileOrdinals = { }; export const percentileFields = { - [Percentiles.P50]: 'monitoring.execution.calculated_metrics.p50', - [Percentiles.P95]: 'monitoring.execution.calculated_metrics.p95', - [Percentiles.P99]: 'monitoring.execution.calculated_metrics.p99', + [Percentiles.P50]: 'monitoring.run.calculated_metrics.p50', + [Percentiles.P95]: 'monitoring.run.calculated_metrics.p95', + [Percentiles.P99]: 'monitoring.run.calculated_metrics.p99', }; const EMPTY_OBJECT = {}; @@ -219,6 +215,8 @@ export const RulesListTable = (props: RulesListTableProps) => { const [currentlyOpenNotify, setCurrentlyOpenNotify] = useState(); const [isLoadingMap, setIsLoadingMap] = useState>({}); + const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome'); + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); const { euiTheme } = useEuiTheme(); @@ -301,58 +299,6 @@ export const RulesListTable = (props: RulesListTableProps) => { [isRuleTypeEditableInContext, onDisableRule, onEnableRule, onRuleChanged] ); - const renderRuleExecutionStatus = useCallback( - (executionStatus: RuleExecutionStatus, rule: RuleTableItem) => { - const healthColor = getHealthColor(executionStatus.status); - const tooltipMessage = - executionStatus.status === 'error' ? `Error: ${executionStatus?.error?.message}` : null; - const isLicenseError = - executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License; - const statusMessage = isLicenseError - ? ALERT_STATUS_LICENSE_ERROR - : rulesStatusesTranslationsMapping[executionStatus.status]; - - const health = ( - - {statusMessage} - - ); - - const healthWithTooltip = tooltipMessage ? ( - - {health} - - ) : ( - health - ); - - return ( - - {healthWithTooltip} - {isLicenseError && ( - - onManageLicenseClick(rule)} - > - - - - )} - - ); - }, - [onManageLicenseClick] - ); - const selectionColumn = useMemo(() => { return { id: 'ruleSelection', @@ -382,6 +328,13 @@ export const RulesListTable = (props: RulesListTableProps) => { }; }, [isPageSelected, onSelectPage, onSelectRow, isRowSelected]); + const ruleOutcomeColumnField = useMemo(() => { + if (isRuleLastRunOutcomeEnabled) { + return 'lastRun.outcome'; + } + return 'executionStatus.status'; + }, [isRuleLastRunOutcomeEnabled]); + const getRulesTableColumns = useCallback((): RulesListColumns[] => { return [ { @@ -684,7 +637,7 @@ export const RulesListTable = (props: RulesListTableProps) => { }, { id: 'ruleExecutionSuccessRatio', - field: 'monitoring.execution.calculated_metrics.success_ratio', + field: 'monitoring.run.calculated_metrics.success_ratio', width: '12%', selectorName: i18n.translate( 'xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.selector.successRatioTitle', @@ -719,7 +672,7 @@ export const RulesListTable = (props: RulesListTableProps) => { }, { id: 'ruleExecutionStatus', - field: 'executionStatus.status', + field: ruleOutcomeColumnField, name: i18n.translate( 'xpack.triggersActionsUI.sections.rulesList.rulesListTable.columns.lastResponseTitle', { defaultMessage: 'Last response' } @@ -729,7 +682,9 @@ export const RulesListTable = (props: RulesListTableProps) => { width: '120px', 'data-test-subj': 'rulesTableCell-lastResponse', render: (_executionStatus: RuleExecutionStatus, rule: RuleTableItem) => { - return renderRuleExecutionStatus(rule.executionStatus, rule); + return ( + + ); }, }, { @@ -827,15 +782,16 @@ export const RulesListTable = (props: RulesListTableProps) => { onRuleEditClick, onSnoozeRule, onUnsnoozeRule, + onManageLicenseClick, renderCollapsedItemActions, renderPercentileCellValue, renderPercentileColumnName, renderRuleError, - renderRuleExecutionStatus, renderRuleStatusDropdown, ruleTypesState.data, selectedPercentile, tagPopoverOpenIndex, + ruleOutcomeColumnField, ]); const allRuleColumns = useMemo(() => getRulesTableColumns(), [getRulesTableColumns]); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table_status_cell.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table_status_cell.test.tsx new file mode 100644 index 00000000000000..33ee761b828dc1 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table_status_cell.test.tsx @@ -0,0 +1,162 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { render } from '@testing-library/react'; +import { + RulesListTableStatusCell, + RulesListTableStatusCellProps, +} from './rules_list_table_status_cell'; +import { RuleTableItem } from '../../../../types'; +import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; + +jest.mock('../../../../common/get_experimental_features', () => ({ + getIsExperimentalFeatureEnabled: jest.fn(), +})); + +const mockRule: RuleTableItem = { + id: '1', + enabled: true, + executionStatus: { + status: 'ok', + }, + lastRun: { + outcome: 'succeeded', + }, + nextRun: new Date('2020-08-20T19:23:38Z'), +} as RuleTableItem; + +const onManageLicenseClickMock = jest.fn(); + +const ComponentWithLocale = (props: RulesListTableStatusCellProps) => { + return ( + + + + ); +}; + +describe('RulesListTableStatusCell', () => { + beforeEach(() => { + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => true); + }); + + afterEach(() => { + onManageLicenseClickMock.mockClear(); + }); + + it('should render successful rule outcome', async () => { + const { getByTestId } = render( + + ); + expect(getByTestId('ruleStatus-succeeded')).not.toBe(null); + }); + + it('should render failed rule outcome', async () => { + const { getByTestId } = render( + + ); + expect(getByTestId('ruleStatus-failed')).not.toBe(null); + }); + + it('should render warning rule outcome', async () => { + const { getByTestId } = render( + + ); + expect(getByTestId('ruleStatus-warning')).not.toBe(null); + }); + + it('should render license errors', async () => { + const { getByTestId, getByText } = render( + + ); + expect(getByTestId('ruleStatus-warning')).not.toBe(null); + expect(getByText('License Error')).not.toBe(null); + }); + + it('should render loading indicator for new rules', async () => { + const { getByText } = render( + + ); + + expect(getByText('Statistic is loading')).not.toBe(null); + }); + + it('should render rule with no last run', async () => { + const { queryByText, getAllByText } = render( + + ); + + expect(queryByText('Statistic is loading')).toBe(null); + expect(getAllByText('--')).not.toBe(null); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table_status_cell.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table_status_cell.tsx new file mode 100644 index 00000000000000..759e16c7f696df --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table_status_cell.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiHealth, + EuiToolTip, + EuiStat, +} from '@elastic/eui'; +import { RuleTableItem } from '../../../../types'; +import { + getRuleHealthColor, + getIsLicenseError, + getRuleStatusMessage, +} from '../../../../common/lib/rule_status_helpers'; +import { + ALERT_STATUS_LICENSE_ERROR, + rulesLastRunOutcomeTranslationMapping, + rulesStatusesTranslationsMapping, +} from '../translations'; + +export interface RulesListTableStatusCellProps { + rule: RuleTableItem; + onManageLicenseClick: (rule: RuleTableItem) => void; +} + +export const RulesListTableStatusCell = (props: RulesListTableStatusCellProps) => { + const { rule, onManageLicenseClick } = props; + const { lastRun } = rule; + + const isLicenseError = getIsLicenseError(rule); + const healthColor = getRuleHealthColor(rule); + const statusMessage = getRuleStatusMessage({ + rule, + licenseErrorText: ALERT_STATUS_LICENSE_ERROR, + lastOutcomeTranslations: rulesLastRunOutcomeTranslationMapping, + executionStatusTranslations: rulesStatusesTranslationsMapping, + }); + const tooltipMessage = lastRun?.outcome === 'failed' ? `Error: ${lastRun?.outcomeMsg}` : null; + + if (!statusMessage) { + return ( + + ); + } + + const health = ( + + {statusMessage} + + ); + + const healthWithTooltip = tooltipMessage ? ( + + {health} + + ) : ( + health + ); + + return ( + + {healthWithTooltip} + {isLicenseError && ( + + onManageLicenseClick(rule)} + > + + + + )} + + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/test_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/test_helpers.ts index 8198d974a85923..545b7d7141a926 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/test_helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/test_helpers.ts @@ -36,7 +36,7 @@ export const mockedRulesData = [ error: null, }, monitoring: { - execution: { + run: { history: [ { success: true, @@ -57,8 +57,18 @@ export const mockedRulesData = [ p95: 300000, p99: 300000, }, + last_run: { + timestamp: '2020-08-20T19:23:38Z', + metrics: { + duration: 500, + }, + }, }, }, + lastRun: { + outcome: 'succeeded', + alertsCount: {}, + }, }, { id: '2', @@ -83,7 +93,7 @@ export const mockedRulesData = [ error: null, }, monitoring: { - execution: { + run: { history: [ { success: true, @@ -100,8 +110,18 @@ export const mockedRulesData = [ p95: 100000, p99: 500000, }, + last_run: { + timestamp: '2020-08-20T19:23:38Z', + metrics: { + duration: 61000, + }, + }, }, }, + lastRun: { + outcome: 'succeeded', + alertsCount: {}, + }, }, { id: '3', @@ -126,11 +146,17 @@ export const mockedRulesData = [ error: null, }, monitoring: { - execution: { + run: { history: [{ success: false, duration: 100 }], calculated_metrics: { success_ratio: 0, }, + last_run: { + timestamp: '2020-08-20T19:23:38Z', + metrics: { + duration: 30234, + }, + }, }, }, }, @@ -159,6 +185,11 @@ export const mockedRulesData = [ message: 'test', }, }, + lastRun: { + outcome: 'failed', + outcomeMsg: 'test', + warning: RuleExecutionStatusErrorReasons.Unknown, + }, }, { id: '5', @@ -185,6 +216,11 @@ export const mockedRulesData = [ message: 'test', }, }, + lastRun: { + outcome: 'failed', + outcomeMsg: 'test', + warning: RuleExecutionStatusErrorReasons.License, + }, }, { id: '6', @@ -211,6 +247,11 @@ export const mockedRulesData = [ message: 'test', }, }, + lastRun: { + outcome: 'warning', + outcomeMsg: 'test', + warning: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, + }, }, ]; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/translations.ts index e69107e060daf2..07675befd8d4fb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/translations.ts @@ -55,6 +55,26 @@ export const ALERT_STATUS_WARNING = i18n.translate( defaultMessage: 'Warning', } ); +export const RULE_LAST_RUN_OUTCOME_SUCCEEDED = i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.ruleLastRunOutcomeSucceeded', + { + defaultMessage: 'Succeeded', + } +); + +export const RULE_LAST_RUN_OUTCOME_WARNING = i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.ruleLastRunOutcomeWarning', + { + defaultMessage: 'Warning', + } +); + +export const RULE_LAST_RUN_OUTCOME_FAILED = i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.ruleLastRunOutcomeFailed', + { + defaultMessage: 'Failed', + } +); export const rulesStatusesTranslationsMapping = { ok: ALERT_STATUS_OK, @@ -65,6 +85,12 @@ export const rulesStatusesTranslationsMapping = { warning: ALERT_STATUS_WARNING, }; +export const rulesLastRunOutcomeTranslationMapping = { + succeeded: RULE_LAST_RUN_OUTCOME_SUCCEEDED, + warning: RULE_LAST_RUN_OUTCOME_WARNING, + failed: RULE_LAST_RUN_OUTCOME_FAILED, +}; + export const ALERT_ERROR_UNKNOWN_REASON = i18n.translate( 'xpack.triggersActionsUI.sections.rulesList.ruleErrorReasonUnknown', { @@ -199,6 +225,93 @@ export const CLEAR_SELECTION = i18n.translate( } ); +export const RULE_STATUS_ACTIVE = (total: number) => { + return i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.totalStatusesActiveDescription', + { + defaultMessage: 'Active: {totalStatusesActive}', + values: { totalStatusesActive: total }, + } + ); +}; + +export const RULE_STATUS_ERROR = (total: number) => { + return i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.totalStatusesErrorDescription', + { + defaultMessage: 'Error: {totalStatusesError}', + values: { totalStatusesError: total }, + } + ); +}; + +export const RULE_STATUS_WARNING = (total: number) => { + return i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.totalStatusesWarningDescription', + { + defaultMessage: 'Warning: {totalStatusesWarning}', + values: { totalStatusesWarning: total }, + } + ); +}; + +export const RULE_STATUS_OK = (total: number) => { + return i18n.translate('xpack.triggersActionsUI.sections.rulesList.totalStatusesOkDescription', { + defaultMessage: 'Ok: {totalStatusesOk}', + values: { totalStatusesOk: total }, + }); +}; + +export const RULE_STATUS_PENDING = (total: number) => { + return i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.totalStatusesPendingDescription', + { + defaultMessage: 'Pending: {totalStatusesPending}', + values: { totalStatusesPending: total }, + } + ); +}; + +export const RULE_STATUS_UNKNOWN = (total: number) => { + return i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.totalStatusesUnknownDescription', + { + defaultMessage: 'Unknown: {totalStatusesUnknown}', + values: { totalStatusesUnknown: total }, + } + ); +}; + +export const RULE_LAST_RUN_OUTCOME_SUCCEEDED_DESCRIPTION = (total: number) => { + return i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.lastRunOutcomeSucceededDescription', + { + defaultMessage: 'Succeeded: {total}', + values: { total }, + } + ); +}; + +export const RULE_LAST_RUN_OUTCOME_WARNING_DESCRIPTION = (total: number) => { + return i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.lastRunOutcomeWarningDescription', + { + defaultMessage: 'Warning: {total}', + values: { total }, + } + ); +}; + +export const RULE_LAST_RUN_OUTCOME_FAILED_DESCRIPTION = (total: number) => { + return i18n.translate( + 'xpack.triggersActionsUI.sections.rulesList.lastRunOutcomeFailedDescription', + { + defaultMessage: 'Failed: {total}', + values: { total }, + } + ); +}; + export const SINGLE_RULE_TITLE = i18n.translate( 'xpack.triggersActionsUI.sections.rulesList.singleTitle', { diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx index 18e2f240dac215..a35599e9927861 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx @@ -20,6 +20,7 @@ describe('getIsExperimentalFeatureEnabled', () => { rulesDetailLogs: true, ruleTagFilter: true, ruleStatusFilter: true, + ruleLastRunOutcome: true, }, }); @@ -43,6 +44,10 @@ describe('getIsExperimentalFeatureEnabled', () => { expect(result).toEqual(true); + result = getIsExperimentalFeatureEnabled('ruleLastRunOutcome'); + + expect(result).toEqual(true); + expect(() => getIsExperimentalFeatureEnabled('doesNotExist' as any)).toThrowError( `Invalid enable value doesNotExist. Allowed values are: ${allowedExperimentalValueKeys.join( ', ' diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts index f7af1fbbde257c..aa5fe263e084fb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts @@ -6,4 +6,11 @@ */ export { getTimeFieldOptions, getTimeOptions } from './get_time_options'; +export { + getOutcomeHealthColor, + getExecutionStatusHealthColor, + getRuleHealthColor, + getIsLicenseError, + getRuleStatusMessage, +} from './rule_status_helpers'; export { useKibana } from './kibana'; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/rule_status_helper.test.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/rule_status_helper.test.ts new file mode 100644 index 00000000000000..91af1e05ece201 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/rule_status_helper.test.ts @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getRuleHealthColor, getRuleStatusMessage } from './rule_status_helpers'; +import { RuleTableItem } from '../../types'; + +import { getIsExperimentalFeatureEnabled } from '../get_experimental_features'; +import { + ALERT_STATUS_LICENSE_ERROR, + rulesLastRunOutcomeTranslationMapping, + rulesStatusesTranslationsMapping, +} from '../../application/sections/rules_list/translations'; + +jest.mock('../get_experimental_features', () => ({ + getIsExperimentalFeatureEnabled: jest.fn(), +})); + +const mockRule = { + id: '1', + enabled: true, + executionStatus: { + status: 'active', + }, + lastRun: { + outcome: 'succeeded', + }, +} as RuleTableItem; + +const warningRule = { + ...mockRule, + executionStatus: { + status: 'warning', + }, + lastRun: { + outcome: 'warning', + }, +} as RuleTableItem; + +const failedRule = { + ...mockRule, + executionStatus: { + status: 'error', + }, + lastRun: { + outcome: 'failed', + }, +} as RuleTableItem; + +const licenseErrorRule = { + ...mockRule, + executionStatus: { + status: 'error', + error: { + reason: 'license', + }, + }, + lastRun: { + outcome: 'failed', + warning: 'license', + }, +} as RuleTableItem; + +beforeEach(() => { + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => true); +}); + +describe('getRuleHealthColor', () => { + it('should return the correct color for successful rule', () => { + let color = getRuleHealthColor(mockRule); + expect(color).toEqual('success'); + + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); + + color = getRuleHealthColor(mockRule); + expect(color).toEqual('success'); + }); + + it('should return the correct color for warning rule', () => { + let color = getRuleHealthColor(warningRule); + expect(color).toEqual('warning'); + + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); + + color = getRuleHealthColor(warningRule); + expect(color).toEqual('warning'); + }); + + it('should return the correct color for failed rule', () => { + let color = getRuleHealthColor(failedRule); + expect(color).toEqual('danger'); + + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); + + color = getRuleHealthColor(failedRule); + expect(color).toEqual('danger'); + }); +}); + +describe('getRuleStatusMessage', () => { + it('should get the status message for a successful rule', () => { + let statusMessage = getRuleStatusMessage({ + rule: mockRule, + licenseErrorText: ALERT_STATUS_LICENSE_ERROR, + lastOutcomeTranslations: rulesLastRunOutcomeTranslationMapping, + executionStatusTranslations: rulesStatusesTranslationsMapping, + }); + expect(statusMessage).toEqual('Succeeded'); + + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); + statusMessage = getRuleStatusMessage({ + rule: mockRule, + licenseErrorText: ALERT_STATUS_LICENSE_ERROR, + lastOutcomeTranslations: rulesLastRunOutcomeTranslationMapping, + executionStatusTranslations: rulesStatusesTranslationsMapping, + }); + expect(statusMessage).toEqual('Active'); + }); + + it('should get the status message for a warning rule', () => { + let statusMessage = getRuleStatusMessage({ + rule: warningRule, + licenseErrorText: ALERT_STATUS_LICENSE_ERROR, + lastOutcomeTranslations: rulesLastRunOutcomeTranslationMapping, + executionStatusTranslations: rulesStatusesTranslationsMapping, + }); + expect(statusMessage).toEqual('Warning'); + + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); + statusMessage = getRuleStatusMessage({ + rule: warningRule, + licenseErrorText: ALERT_STATUS_LICENSE_ERROR, + lastOutcomeTranslations: rulesLastRunOutcomeTranslationMapping, + executionStatusTranslations: rulesStatusesTranslationsMapping, + }); + expect(statusMessage).toEqual('Warning'); + }); + + it('should get the status message for a failed rule', () => { + let statusMessage = getRuleStatusMessage({ + rule: failedRule, + licenseErrorText: ALERT_STATUS_LICENSE_ERROR, + lastOutcomeTranslations: rulesLastRunOutcomeTranslationMapping, + executionStatusTranslations: rulesStatusesTranslationsMapping, + }); + expect(statusMessage).toEqual('Failed'); + + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); + statusMessage = getRuleStatusMessage({ + rule: failedRule, + licenseErrorText: ALERT_STATUS_LICENSE_ERROR, + lastOutcomeTranslations: rulesLastRunOutcomeTranslationMapping, + executionStatusTranslations: rulesStatusesTranslationsMapping, + }); + expect(statusMessage).toEqual('Error'); + }); + + it('should get the status message for a license error rule', () => { + let statusMessage = getRuleStatusMessage({ + rule: licenseErrorRule, + licenseErrorText: ALERT_STATUS_LICENSE_ERROR, + lastOutcomeTranslations: rulesLastRunOutcomeTranslationMapping, + executionStatusTranslations: rulesStatusesTranslationsMapping, + }); + expect(statusMessage).toEqual('License Error'); + + (getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false); + statusMessage = getRuleStatusMessage({ + rule: licenseErrorRule, + licenseErrorText: ALERT_STATUS_LICENSE_ERROR, + lastOutcomeTranslations: rulesLastRunOutcomeTranslationMapping, + executionStatusTranslations: rulesStatusesTranslationsMapping, + }); + expect(statusMessage).toEqual('License Error'); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/rule_status_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/rule_status_helpers.ts new file mode 100644 index 00000000000000..f3e6419e4e2faa --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/rule_status_helpers.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + RuleLastRunOutcomes, + RuleExecutionStatuses, + RuleExecutionStatusErrorReasons, +} from '@kbn/alerting-plugin/common'; +import { getIsExperimentalFeatureEnabled } from '../get_experimental_features'; +import { Rule } from '../../types'; + +export const getOutcomeHealthColor = (status: RuleLastRunOutcomes) => { + switch (status) { + case 'succeeded': + return 'success'; + case 'failed': + return 'danger'; + case 'warning': + return 'warning'; + default: + return 'subdued'; + } +}; + +export const getExecutionStatusHealthColor = (status: RuleExecutionStatuses) => { + switch (status) { + case 'active': + return 'success'; + case 'error': + return 'danger'; + case 'ok': + return 'primary'; + case 'pending': + return 'accent'; + case 'warning': + return 'warning'; + default: + return 'subdued'; + } +}; + +export const getRuleHealthColor = (rule: Rule) => { + const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome'); + if (isRuleLastRunOutcomeEnabled) { + return (rule.lastRun && getOutcomeHealthColor(rule.lastRun.outcome)) || 'subdued'; + } + return getExecutionStatusHealthColor(rule.executionStatus.status); +}; + +export const getIsLicenseError = (rule: Rule) => { + return ( + rule.lastRun?.warning === RuleExecutionStatusErrorReasons.License || + rule.executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License + ); +}; + +export const getRuleStatusMessage = ({ + rule, + licenseErrorText, + lastOutcomeTranslations, + executionStatusTranslations, +}: { + rule: Rule; + licenseErrorText: string; + lastOutcomeTranslations: Record; + executionStatusTranslations: Record; +}) => { + const isLicenseError = getIsLicenseError(rule); + const isRuleLastRunOutcomeEnabled = getIsExperimentalFeatureEnabled('ruleLastRunOutcome'); + + if (isLicenseError) { + return licenseErrorText; + } + if (isRuleLastRunOutcomeEnabled) { + return rule.lastRun && lastOutcomeTranslations[rule.lastRun.outcome]; + } + return executionStatusTranslations[rule.executionStatus.status]; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index d8ebc2298dca9a..9bfc31db44c554 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -23,6 +23,7 @@ import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { triggersActionsRoute } from '@kbn/rule-data-utils'; import type { AlertsSearchBarProps } from './application/sections/alerts_search_bar'; import { TypeRegistry } from './application/type_registry'; @@ -212,7 +213,7 @@ export class Plugin title: featureTitle, description: featureDescription, icon: 'watchesApp', - path: '/app/management/insightsAndAlerting/triggersActions', + path: triggersActionsRoute, showOnHomePage: false, category: 'admin', }); @@ -221,7 +222,7 @@ export class Plugin title: connectorsFeatureTitle, description: connectorsFeatureDescription, icon: 'watchesApp', - path: '/app/management/insightsAndAlerting/triggersActions', + path: triggersActionsRoute, showOnHomePage: false, category: 'admin', }); diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index a0905a8645fbc9..415672324e648a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -40,6 +40,7 @@ import { RuleTypeParams, ActionVariable, RuleType as CommonRuleType, + RuleLastRun, } from '@kbn/alerting-plugin/common'; import type { BulkOperationError } from '@kbn/alerting-plugin/server'; import { RuleRegistrySearchRequestPagination } from '@kbn/rule-registry-plugin/common'; @@ -106,6 +107,7 @@ export type { RuleStatusDropdownProps, RuleTagFilterProps, RuleStatusFilterProps, + RuleLastRun, RuleTagBadgeProps, RuleTagBadgeOptions, RuleEventLogListProps, @@ -301,7 +303,7 @@ export interface RuleType< export type SanitizedRuleType = Omit; -export type RuleUpdates = Omit; +export type RuleUpdates = Omit; export interface RuleTableItem extends Rule { ruleType: RuleType['name']; diff --git a/x-pack/plugins/upgrade_assistant/public/application/app.tsx b/x-pack/plugins/upgrade_assistant/public/application/app.tsx index b4acb7e88d1a32..39d4b5602b140e 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/app.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app.tsx @@ -189,9 +189,11 @@ export const App = ({ history }: { history: ScopedHistory }) => { export const RootComponent = (dependencies: AppDependencies) => { const { history, - core: { i18n, application, http }, + core: { i18n, application, http, executionContext }, } = dependencies.services; + executionContext.set({ type: 'application', page: 'upgradeAssistant' }); + return ( diff --git a/x-pack/test/accessibility/apps/rules_connectors.ts b/x-pack/test/accessibility/apps/rules_connectors.ts new file mode 100644 index 00000000000000..bc177937172822 --- /dev/null +++ b/x-pack/test/accessibility/apps/rules_connectors.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. + */ + +// a11y tests for rules, logs and connectors page + +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['settings', 'common']); + const a11y = getService('a11y'); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + const toasts = getService('toasts'); + + // Failing: See https://github.com/elastic/kibana/issues/145452 + describe.skip('Kibana Alerts - rules tab accessibility tests', () => { + before(async () => { + await PageObjects.settings.navigateTo(); + await testSubjects.click('triggersActions'); + }); + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + + it('a11y test on rules and connectors main page', async () => { + await a11y.testAppSnapshot(); + }); + + it('a11y test on create rules panel', async () => { + await testSubjects.click('createFirstRuleButton'); + await a11y.testAppSnapshot(); + }); + // https://github.com/elastic/kibana/issues/144953 + it.skip('a11y test on inputs on rules panel', async () => { + await testSubjects.click('ruleNameInput'); + await testSubjects.setValue('ruleNameInput', 'testRule'); + await testSubjects.click('tagsComboBox'); + await testSubjects.setValue('tagsComboBox', 'ruleTag'); + await testSubjects.click('intervalFormRow'); + await testSubjects.click('notifyWhenSelect'); + await testSubjects.click('onActiveAlert'); + await testSubjects.click('solutionsFilterButton'); + await a11y.testAppSnapshot(); + await testSubjects.click('solutionapmFilterOption'); + await testSubjects.setValue('solutionsFilterButton', 'solutionapmFilterOption'); + await testSubjects.click('apm.anomaly-SelectOption'); + await a11y.testAppSnapshot(); + }); + // https://github.com/elastic/kibana/issues/144953 + it.skip('a11y test on save rule without connectors panel', async () => { + await toasts.dismissAllToasts(); + await testSubjects.click('saveRuleButton'); + await a11y.testAppSnapshot(); + }); + // https://github.com/elastic/kibana/issues/144953 + it.skip('a11y test on alerts and logs page with one rule populated', async () => { + await testSubjects.click('confirmModalConfirmButton'); + await a11y.testAppSnapshot(); + await testSubjects.click('checkboxSelectAll'); + await testSubjects.click('deleteActionHoverButton'); + await testSubjects.click('confirmModalConfirmButton'); + }); + + // uncomment after rules tests a11y violations get fixed + it.skip('a11y test on logs tab', async () => { + await testSubjects.click('logsTab'); + await a11y.testAppSnapshot(); + }); + + it('a11y test on connectors tab with create first connector message screen', async () => { + await PageObjects.settings.navigateTo(); + await testSubjects.click('triggersActionsConnectors'); + await a11y.testAppSnapshot(); + }); + + it('a11y test on create connector panel', async () => { + await testSubjects.click('createFirstActionButton'); + await a11y.testAppSnapshot(); + }); + + // Adding a11y test for one connector + it('a11y test on email connectors', async () => { + await testSubjects.click('.email-card'); + await a11y.testAppSnapshot(); + await testSubjects.click('create-connector-flyout-back-btn'); + }); + }); +} diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts index 524608b1307790..191a8b403c41b9 100644 --- a/x-pack/test/accessibility/config.ts +++ b/x-pack/test/accessibility/config.ts @@ -41,6 +41,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/graph'), require.resolve('./apps/security_solution'), require.resolve('./apps/ml_embeddables_in_dashboard'), + require.resolve('./apps/rules_connectors'), // Please make sure that the remote clusters, snapshot and restore and // CCR tests stay in that order. Their execution fails if rearranged. require.resolve('./apps/remote_clusters'), diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_enable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_enable.ts new file mode 100644 index 00000000000000..a99aaf1352deed --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_enable.ts @@ -0,0 +1,499 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 = { total: 1, errors: [], taskIdsFailedToBeEnabled: [] }; + +// 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('bulkEnableRules', () => { + 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 enable of one rule appropriately based on id', async () => { + const { body: createdRule } = 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_enable`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule.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, createdRule.id, 'rule', 'alerting'); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkEnable a "test.noop" rule for "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule.id, 'rule', 'alerting'); + 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); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle bulk enable of one rule appropriately based on id when consumer is the same as producer', async () => { + const { body: createdRule } = 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_enable`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule.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, createdRule.id, 'rule', 'alerting'); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: + 'Unauthorized to bulkEnable a "test.restricted-noop" rule for "alertsRestrictedFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule.id, 'rule', 'alerting'); + 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 enable', + }); + expect(response.statusCode).to.eql(400); + objectRemover.add(space.id, createdRule.id, 'rule', 'alerting'); + 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); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle enable alert request appropriately when consumer is not the producer', async () => { + const { body: createdRule } = 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_enable`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule.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, createdRule.id, 'rule', 'alerting'); + 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 enable', + }); + expect(response.statusCode).to.eql(400); + objectRemover.add(space.id, createdRule.id, 'rule', 'alerting'); + break; + case 'superuser at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle enable alert request appropriately when consumer is "alerts"', async () => { + const { body: createdRule } = 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_enable`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule.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, createdRule.id, 'rule', 'alerting'); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkEnable a "test.noop" rule by "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule.id, 'rule', 'alerting'); + 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); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle bulk enable 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_enable`) + .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'); + }) + ); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkEnable 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'); + }) + ); + 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); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle bulk enable 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-enable'] })) + .expect(200) + ) + ); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_enable`) + .set('kbn-xsrf', 'foo') + .send({ filter: `alert.attributes.tags: "multiple-rules-enable"` }) + .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'); + }) + ); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkEnable 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'); + }) + ); + 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); + await Promise.all( + rules.map((rule) => { + objectRemover.add(space.id, rule.body.id, 'rule', 'alerting'); + }) + ); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should not enable 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_enable`) + .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); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkEnable 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'); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + }); + } + + describe('Validation tests', () => { + const { user, space } = SuperuserAtSpace1; + it('should throw an error when bulk enable 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_enable`) + .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'); + }); + + 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_enable`) + .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_enable`) + .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_enable`) + .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_enable`) + .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/clone.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/clone.ts new file mode 100644 index 00000000000000..329782a79c3c3e --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/clone.ts @@ -0,0 +1,231 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { Spaces, UserAtSpaceScenarios } from '../../../scenarios'; +import { + checkAAD, + getTestRuleData, + getConsumerUnauthorizedErrorMessage, + getUrlPrefix, + ObjectRemover, + TaskManagerDoc, +} from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +interface RuleSpace { + body: any; +} + +// eslint-disable-next-line import/no-default-export +export default function createAlertTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('es'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + describe('clone', async () => { + const objectRemover = new ObjectRemover(supertest); + const space1 = Spaces[0].id; + const space2 = Spaces[1].id; + let ruleSpace1: RuleSpace = { body: {} }; + let ruleSpace2: RuleSpace = { body: {} }; + after(() => objectRemover.removeAll()); + before(async () => { + const { body: createdActionSpace1 } = await supertest + .post(`${getUrlPrefix(space1)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const { body: createdActionSpace2 } = await supertest + .post(`${getUrlPrefix(space2)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + ruleSpace1 = await supertest + .post(`${getUrlPrefix(space1)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + actions: [ + { + id: createdActionSpace1.id, + group: 'default', + params: {}, + }, + ], + }) + ); + objectRemover.add(space1, ruleSpace1.body.id, 'rule', 'alerting'); + + ruleSpace2 = await supertest + .post(`${getUrlPrefix(space2)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + actions: [ + { + id: createdActionSpace2.id, + group: 'default', + params: {}, + }, + ], + }) + ); + objectRemover.add(space2, ruleSpace2.body.id, 'rule', 'alerting'); + }); + + async function getScheduledTask(id: string): Promise { + const scheduledTask = await es.get({ + id: `task:${id}`, + index: '.kibana_task_manager', + }); + return scheduledTask._source!; + } + + for (const scenario of UserAtSpaceScenarios) { + const { user, space } = scenario; + describe(scenario.id, () => { + it('should handle clone rule request appropriately', async () => { + const ruleIdToClone = + space.id === space1 + ? ruleSpace1.body.id + : space.id === space2 + ? ruleSpace2.body.id + : null; + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(space.id)}/internal/alerting/rule/${ruleIdToClone}/_clone`) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .send(); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'global_read at space1': + case 'space_1_all at space2': + expect(response.statusCode).to.eql(403); + expect(response.body).to.eql({ + error: 'Forbidden', + message: getConsumerUnauthorizedErrorMessage( + 'create', + 'test.noop', + 'alertsFixture' + ), + statusCode: 403, + }); + 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.statusCode).to.eql(200); + objectRemover.add(space.id, response.body.id, 'rule', 'alerting'); + expect(response.body).to.eql({ + id: response.body.id, + name: 'abc [Clone]', + tags: ['foo'], + actions: [ + { + id: response.body.actions[0].id, + connector_type_id: response.body.actions[0].connector_type_id, + group: 'default', + params: {}, + }, + ], + enabled: true, + rule_type_id: 'test.noop', + consumer: 'alertsFixture', + params: {}, + created_by: user.username, + schedule: { interval: '1m' }, + scheduled_task_id: response.body.scheduled_task_id, + created_at: response.body.created_at, + updated_at: response.body.updated_at, + throttle: '1m', + notify_when: 'onThrottleInterval', + updated_by: user.username, + api_key_owner: user.username, + mute_all: false, + muted_alert_ids: [], + execution_status: response.body.execution_status, + last_run: { + alerts_count: { + active: 0, + ignored: 0, + new: 0, + recovered: 0, + }, + outcome: 'succeeded', + outcome_msg: null, + warning: null, + }, + next_run: response.body.next_run, + }); + expect(typeof response.body.scheduled_task_id).to.be('string'); + expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); + expect(Date.parse(response.body.updated_at)).to.be.greaterThan(0); + + const taskRecord = await getScheduledTask(response.body.scheduled_task_id); + expect(taskRecord.type).to.eql('task'); + expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); + expect(JSON.parse(taskRecord.task.params)).to.eql({ + alertId: response.body.id, + spaceId: space.id, + consumer: 'alertsFixture', + }); + expect(taskRecord.task.enabled).to.eql(true); + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: space.id, + type: 'alert', + id: response.body.id, + }); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + }); + } + + it('should throw an error when trying to duplicate a rule who belongs to security solution', async () => { + const ruleCreated = await supertest + .post(`${getUrlPrefix(space1)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.unrestricted-noop', + consumer: 'siem', + }) + ); + objectRemover.add(space1, ruleCreated.body.id, 'rule', 'alerting'); + + const cloneRuleResponse = await supertest + .post(`${getUrlPrefix(space1)}/internal/alerting/rule/${ruleCreated.body.id}/_clone`) + .set('kbn-xsrf', 'foo') + .send(); + + expect(cloneRuleResponse.body).to.eql({ + error: 'Bad Request', + message: 'The clone functionality is not enable for rule who belongs to security solution', + 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 6265cb7d34ff93..063301d5f751a6 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 @@ -32,7 +32,9 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./rule_types')); loadTestFile(require.resolve('./bulk_edit')); loadTestFile(require.resolve('./bulk_delete')); + loadTestFile(require.resolve('./bulk_enable')); loadTestFile(require.resolve('./retain_api_key')); + loadTestFile(require.resolve('./clone')); }); }); } diff --git a/x-pack/test/api_integration/apis/monitoring/kibana/instance_mb.js b/x-pack/test/api_integration/apis/monitoring/kibana/instance_mb.js index 4aaa71101f43c3..ca7281d9c1eb1b 100644 --- a/x-pack/test/api_integration/apis/monitoring/kibana/instance_mb.js +++ b/x-pack/test/api_integration/apis/monitoring/kibana/instance_mb.js @@ -15,34 +15,37 @@ export default function ({ getService }) { const supertest = getService('supertest'); const { setup, tearDown } = getLifecycleMethods(getService); - describe('instance detail mb', () => { - const archive = - 'x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_mb'; - const timeRange = { - min: '2017-08-29T17:24:17.000Z', - max: '2017-08-29T17:26:08.000Z', - }; + describe('instance detail - metricbeat and package', () => { + ['mb', 'package'].forEach((source) => { + describe(`instance detail ${source}`, () => { + const archive = `x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_${source}`; + const timeRange = { + min: '2017-08-29T17:24:17.000Z', + max: '2017-08-29T17:26:08.000Z', + }; - before('load archive', () => { - return setup(archive); - }); + before('load archive', () => { + return setup(archive); + }); - after('unload archive', () => { - return tearDown(); - }); + after('unload archive', () => { + return tearDown(archive); + }); - it('should summarize single kibana instance with metrics', async () => { - const { body } = await supertest - .post( - '/api/monitoring/v1/clusters/DFDDUmKHR0Ge0mkdYW2bew/kibana/de3b8f2a-7bb9-4931-9bf3-997ba7824cf9' - ) - .set('kbn-xsrf', 'xxx') - .send({ timeRange }) - .expect(200); + it('should summarize single kibana instance with metrics', async () => { + const { body } = await supertest + .post( + '/api/monitoring/v1/clusters/DFDDUmKHR0Ge0mkdYW2bew/kibana/de3b8f2a-7bb9-4931-9bf3-997ba7824cf9' + ) + .set('kbn-xsrf', 'xxx') + .send({ timeRange }) + .expect(200); - body.metrics = normalizeDataTypeDifferences(body.metrics, instanceFixture); - instanceFixture.metrics = setIndicesFound(instanceFixture.metrics, true); - expect(body).to.eql(instanceFixture); + body.metrics = normalizeDataTypeDifferences(body.metrics, instanceFixture); + instanceFixture.metrics = setIndicesFound(instanceFixture.metrics, true); + expect(body).to.eql(instanceFixture); + }); + }); }); }); } diff --git a/x-pack/test/api_integration/apis/monitoring/kibana/listing_mb.js b/x-pack/test/api_integration/apis/monitoring/kibana/listing_mb.js index 90ea1d40ca9170..1068109a15b165 100644 --- a/x-pack/test/api_integration/apis/monitoring/kibana/listing_mb.js +++ b/x-pack/test/api_integration/apis/monitoring/kibana/listing_mb.js @@ -13,36 +13,39 @@ export default function ({ getService }) { const supertest = getService('supertest'); const { setup, tearDown } = getLifecycleMethods(getService); - describe('listing mb', () => { - const archive = - 'x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_mb'; - const timeRange = { - min: '2017-08-29T17:24:17.000Z', - max: '2017-08-29T17:26:08.000Z', - }; - - before('load archive', () => { - return setup(archive); - }); - - after('unload archive', () => { - return tearDown(); - }); - - it('should summarize list of kibana instances with stats', async () => { - const { body } = await supertest - .post('/api/monitoring/v1/clusters/DFDDUmKHR0Ge0mkdYW2bew/kibana/instances') - .set('kbn-xsrf', 'xxx') - .send({ timeRange }) - .expect(200); - - // Fixture is shared between internal and Metricbeat collection tests - // But timestamps of documents differ by a few miliseconds - const lastSeenTimestamp = body.kibanas[0].lastSeenTimestamp; - delete body.kibanas[0].lastSeenTimestamp; - - expect(body).to.eql(listingFixture); - expect(lastSeenTimestamp).to.eql('2017-08-29T17:25:43.192Z'); + describe('listing - metricbeat and package', () => { + ['mb', 'package'].forEach((source) => { + describe(`listing ${source}`, () => { + const archive = `x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_${source}`; + const timeRange = { + min: '2017-08-29T17:24:17.000Z', + max: '2017-08-29T17:26:08.000Z', + }; + + before('load archive', () => { + return setup(archive); + }); + + after('unload archive', () => { + return tearDown(archive); + }); + + it('should summarize list of kibana instances with stats', async () => { + const { body } = await supertest + .post('/api/monitoring/v1/clusters/DFDDUmKHR0Ge0mkdYW2bew/kibana/instances') + .set('kbn-xsrf', 'xxx') + .send({ timeRange }) + .expect(200); + + // Fixture is shared between internal and Metricbeat collection tests + // But timestamps of documents differ by a few miliseconds + const lastSeenTimestamp = body.kibanas[0].lastSeenTimestamp; + delete body.kibanas[0].lastSeenTimestamp; + + expect(body).to.eql(listingFixture); + expect(lastSeenTimestamp).to.eql('2017-08-29T17:25:43.192Z'); + }); + }); }); }); } diff --git a/x-pack/test/api_integration/apis/monitoring/kibana/overview_mb.js b/x-pack/test/api_integration/apis/monitoring/kibana/overview_mb.js index 46edaa0d1ff13d..e4923f4940c270 100644 --- a/x-pack/test/api_integration/apis/monitoring/kibana/overview_mb.js +++ b/x-pack/test/api_integration/apis/monitoring/kibana/overview_mb.js @@ -15,32 +15,35 @@ export default function ({ getService }) { const supertest = getService('supertest'); const { setup, tearDown } = getLifecycleMethods(getService); - describe('overview mb', () => { - const archive = - 'x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_mb'; - const timeRange = { - min: '2017-08-29T17:24:17.000Z', - max: '2017-08-29T17:26:08.000Z', - }; + describe('overview - metricbeat and package', () => { + ['mb', 'package'].forEach((source) => { + describe(`overview ${source}`, () => { + const archive = `x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_${source}`; + const timeRange = { + min: '2017-08-29T17:24:17.000Z', + max: '2017-08-29T17:26:08.000Z', + }; - before('load archive', () => { - return setup(archive); - }); + before('load archive', () => { + return setup(archive); + }); - after('unload archive', () => { - return tearDown(); - }); + after('unload archive', () => { + return tearDown(archive); + }); - it('should summarize kibana instances with stats', async () => { - const { body } = await supertest - .post('/api/monitoring/v1/clusters/DFDDUmKHR0Ge0mkdYW2bew/kibana') - .set('kbn-xsrf', 'xxx') - .send({ timeRange }) - .expect(200); + it('should summarize kibana instances with stats', async () => { + const { body } = await supertest + .post('/api/monitoring/v1/clusters/DFDDUmKHR0Ge0mkdYW2bew/kibana') + .set('kbn-xsrf', 'xxx') + .send({ timeRange }) + .expect(200); - body.metrics = normalizeDataTypeDifferences(body.metrics, overviewFixture); - overviewFixture.metrics = setIndicesFound(overviewFixture.metrics, true); - expect(body).to.eql(overviewFixture); + body.metrics = normalizeDataTypeDifferences(body.metrics, overviewFixture); + overviewFixture.metrics = setIndicesFound(overviewFixture.metrics, true); + expect(body).to.eql(overviewFixture); + }); + }); }); }); } diff --git a/x-pack/test/api_integration/apis/monitoring/kibana/rules_and_actions/instance.js b/x-pack/test/api_integration/apis/monitoring/kibana/rules_and_actions/instance.js index a16a053dde3a15..76bd34ece039b6 100644 --- a/x-pack/test/api_integration/apis/monitoring/kibana/rules_and_actions/instance.js +++ b/x-pack/test/api_integration/apis/monitoring/kibana/rules_and_actions/instance.js @@ -13,30 +13,34 @@ export default function ({ getService }) { const supertest = getService('supertest'); const { setup, tearDown } = getLifecycleMethods(getService); - describe('instance detail', () => { - const archive = 'x-pack/test/functional/es_archives/monitoring/kibana/rules_and_actions'; - const timeRange = { - min: '2022-05-31T18:44:19.267Z', - max: '2022-05-31T19:59:19.267Z', - }; + describe('instance detail - metricbeat and package', () => { + ['rules_and_actions', 'rules_and_actions_package'].forEach((source) => { + describe(`instance detail ${source}`, () => { + const archive = `x-pack/test/functional/es_archives/monitoring/kibana/${source}`; + const timeRange = { + min: '2022-05-31T18:44:19.267Z', + max: '2022-05-31T19:59:19.267Z', + }; - before('load archive', () => { - return setup(archive); - }); + before('load archive', () => { + return setup(archive); + }); - after('unload archive', () => { - return tearDown(); - }); + after('unload archive', () => { + return tearDown(archive); + }); - it('should get data for the kibana instance view', async () => { - const { body } = await supertest - .post( - '/api/monitoring/v1/clusters/SvjwrFv6Rvuqjm9-cSSVEg/kibana/5b2de169-2785-441b-ae8c-186a1936b17d' - ) - .set('kbn-xsrf', 'xxx') - .send({ timeRange }) - .expect(200); - expect(body).to.eql(fixture); + it('should get data for the kibana instance view', async () => { + const { body } = await supertest + .post( + '/api/monitoring/v1/clusters/SvjwrFv6Rvuqjm9-cSSVEg/kibana/5b2de169-2785-441b-ae8c-186a1936b17d' + ) + .set('kbn-xsrf', 'xxx') + .send({ timeRange }) + .expect(200); + expect(body).to.eql(fixture); + }); + }); }); }); } diff --git a/x-pack/test/api_integration/apis/monitoring/kibana/rules_and_actions/overview.js b/x-pack/test/api_integration/apis/monitoring/kibana/rules_and_actions/overview.js index 720e9425875120..c7861a803ef913 100644 --- a/x-pack/test/api_integration/apis/monitoring/kibana/rules_and_actions/overview.js +++ b/x-pack/test/api_integration/apis/monitoring/kibana/rules_and_actions/overview.js @@ -13,29 +13,33 @@ export default function ({ getService }) { const supertest = getService('supertest'); const { setup, tearDown } = getLifecycleMethods(getService); - describe('overview', () => { - const archive = 'x-pack/test/functional/es_archives/monitoring/kibana/rules_and_actions'; - const timeRange = { - min: '2022-05-31T18:44:19.267Z', - max: '2022-05-31T19:59:19.267Z', - }; + describe('overview - metricbeat and package', () => { + ['rules_and_actions', 'rules_and_actions_package'].forEach((source) => { + describe(`overview ${source}`, () => { + const archive = `x-pack/test/functional/es_archives/monitoring/kibana/${source}`; + const timeRange = { + min: '2022-05-31T18:44:19.267Z', + max: '2022-05-31T19:59:19.267Z', + }; - before('load archive', () => { - return setup(archive); - }); + before('load archive', () => { + return setup(archive); + }); - after('unload archive', () => { - return tearDown(); - }); + after('unload archive', () => { + return tearDown(archive); + }); - it('should get kibana rules at cluster level', async () => { - const { body } = await supertest - .post('/api/monitoring/v1/clusters/SvjwrFv6Rvuqjm9-cSSVEg') - .set('kbn-xsrf', 'xxx') - .send({ timeRange, codePaths: ['all'] }) - .expect(200); + it('should get kibana rules at cluster level', async () => { + const { body } = await supertest + .post('/api/monitoring/v1/clusters/SvjwrFv6Rvuqjm9-cSSVEg') + .set('kbn-xsrf', 'xxx') + .send({ timeRange, codePaths: ['all'] }) + .expect(200); - expect(body[0].kibana.rules).to.eql(fixture[0].kibana.rules); + expect(body[0].kibana.rules).to.eql(fixture[0].kibana.rules); + }); + }); }); }); } diff --git a/x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts b/x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts index f700967d060be4..575de013a96dd0 100644 --- a/x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts +++ b/x-pack/test/api_integration/apis/synthetics/get_monitor_overview.ts @@ -13,7 +13,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; export default function ({ getService }: FtrProviderContext) { - describe('GetMonitorsOverview', function () { + // Failing: See https://github.com/elastic/kibana/issues/145270 + describe.skip('GetMonitorsOverview', function () { this.tags('skipCloud'); const supertest = getService('supertest'); diff --git a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.spec.ts b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.spec.ts index e8e06b10794c94..e9c8167b0a8784 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.spec.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.spec.ts @@ -183,16 +183,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } - const failedtransactionsFieldStats = await apmApiClient.readUser({ - endpoint: 'POST /internal/apm/correlations/field_stats/transactions', - params: { - body: { - ...getOptions(), - fieldsToSample: [...fieldsToSample], - }, - }, - }); - const finalRawResponse: FailedTransactionsCorrelationsResponse = { ccsWarning: failedTransactionsCorrelationsResponse.body?.ccsWarning, percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue, @@ -200,13 +190,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { errorHistogram: errorDistributionResponse.body?.overallHistogram, failedTransactionsCorrelations: failedTransactionsCorrelationsResponse.body?.failedTransactionsCorrelations, - fieldStats: failedtransactionsFieldStats.body?.stats, }; expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875); expect(finalRawResponse?.errorHistogram?.length).to.be(101); expect(finalRawResponse?.overallHistogram?.length).to.be(101); - expect(finalRawResponse?.fieldStats?.length).to.be(fieldsToSample.size); expect(finalRawResponse?.failedTransactionsCorrelations?.length).to.eql( 30, @@ -228,13 +216,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(typeof correlation?.normalizedScore).to.be('number'); expect(typeof correlation?.failurePercentage).to.be('number'); expect(typeof correlation?.successPercentage).to.be('number'); - - const fieldStats = finalRawResponse?.fieldStats?.[0]; - expect(typeof fieldStats).to.be('object'); - expect(Array.isArray(fieldStats?.topValues) && fieldStats?.topValues?.length).to.greaterThan( - 0 - ); - expect(fieldStats?.topValuesSampleSize).to.greaterThan(0); }); }); } diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency.spec.ts b/x-pack/test/apm_api_integration/tests/correlations/latency.spec.ts index d0eea80dcf1c06..a4edfd1d5ab007 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/latency.spec.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/latency.spec.ts @@ -220,28 +220,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } - const failedtransactionsFieldStats = await apmApiClient.readUser({ - endpoint: 'POST /internal/apm/correlations/field_stats/transactions', - params: { - body: { - ...getOptions(), - fieldsToSample: [...fieldsToSample], - }, - }, - }); - const finalRawResponse: LatencyCorrelationsResponse = { ccsWarning, percentileThresholdValue: overallDistributionResponse.body?.percentileThresholdValue, overallHistogram: overallDistributionResponse.body?.overallHistogram, latencyCorrelations, - fieldStats: failedtransactionsFieldStats.body?.stats, }; // Fetched 95th percentile value of 1309695.875 based on 1244 documents. expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875); expect(finalRawResponse?.overallHistogram?.length).to.be(101); - expect(finalRawResponse?.fieldStats?.length).to.be(fieldsToSample.size); // Identified 13 significant correlations out of 379 field/value pairs. expect(finalRawResponse?.latencyCorrelations?.length).to.eql( @@ -258,13 +246,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(correlation?.correlation).to.be(0.6275246559191225); expect(correlation?.ksTest).to.be(4.806503252860024e-13); expect(correlation?.histogram?.length).to.be(101); - - const fieldStats = finalRawResponse?.fieldStats?.[0]; - expect(typeof fieldStats).to.be('object'); - expect( - Array.isArray(fieldStats?.topValues) && fieldStats?.topValues?.length - ).to.greaterThan(0); - expect(fieldStats?.topValuesSampleSize).to.greaterThan(0); }); } ); 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 cd961fce7aed01..c7ac470f1c8f2d 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 @@ -18,7 +18,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); /** - * Specific api integration tests for threat matching rule type + * Specific api integration tests for new terms rule type */ describe('create_new_terms', () => { afterEach(async () => { 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 82cb42c0039c32..5a9777e7f2e79b 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 @@ -310,7 +310,11 @@ export default ({ getService }: FtrProviderContext): void => { await createRule(supertest, log, ruleToDuplicate); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.duplicate }) + .send({ + query: '', + action: BulkActionType.duplicate, + duplicate: { include_exceptions: false }, + }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); @@ -352,7 +356,11 @@ export default ({ getService }: FtrProviderContext): void => { ); const { body } = await postBulkAction() - .send({ query: '', action: BulkActionType.duplicate }) + .send({ + query: '', + action: BulkActionType.duplicate, + duplicate: { include_exceptions: false }, + }) .expect(200); expect(body.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 }); 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 index 8090e4d2ce7097..8ef0bf6b736dda 100644 --- 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 @@ -12,6 +12,10 @@ import { ALERT_RULE_RULE_ID, ALERT_SEVERITY, ALERT_WORKFLOW_STATUS, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_TERMS, } from '@kbn/rule-data-utils'; import { flattenWithPrefix } from '@kbn/securitysolution-rules'; @@ -422,5 +426,238 @@ export default ({ getService }: FtrProviderContext) => { const previewAlerts = await getPreviewAlerts({ es, previewId }); expect(previewAlerts.length).to.eql(1); }); + + describe('with suppression enabled', async () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/security_solution/suppression'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/suppression'); + }); + + it('should generate only 1 alert per host name when grouping by host name', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['suppression-data']), + query: `host.name: "host-0"`, + alert_suppression: { + group_by: ['host.name'], + }, + from: 'now-1h', + interval: '1h', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T05:30:00.000Z'), + }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).to.eql(1); + expect(previewAlerts[0]._source).to.eql({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: 'host-0', + }, + ], + [ALERT_ORIGINAL_TIME]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_START]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_END]: '2020-10-28T05:00:02.000Z', + [ALERT_SUPPRESSION_DOCS_COUNT]: 5, + }); + }); + + it('should generate multiple alerts when multiple host names are found', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['suppression-data']), + query: `host.name: *`, + alert_suppression: { + group_by: ['host.name'], + }, + from: 'now-1h', + interval: '1h', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T05:30:00.000Z'), + }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: 1000 }); + expect(previewAlerts.length).to.eql(3); + + previewAlerts.sort((a, b) => + (a._source?.host?.name ?? '0') > (b._source?.host?.name ?? '0') ? 1 : -1 + ); + + const hostNames = previewAlerts.map((alert) => alert._source?.host?.name); + expect(hostNames).to.eql(['host-0', 'host-1', 'host-2']); + expect(previewAlerts[0]._source).to.eql({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: 'host-0', + }, + ], + [ALERT_ORIGINAL_TIME]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_START]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_END]: '2020-10-28T05:00:02.000Z', + [ALERT_SUPPRESSION_DOCS_COUNT]: 5, + }); + }); + + it('should generate alerts when using multiple group by fields', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['suppression-data']), + query: `host.name: *`, + alert_suppression: { + group_by: ['host.name', 'source.ip'], + }, + from: 'now-1h', + interval: '1h', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T05:30:00.000Z'), + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + size: 1000, + sort: ['host.name', 'source.ip'], + }); + expect(previewAlerts.length).to.eql(6); + + expect(previewAlerts[0]._source).to.eql({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: 'host-0', + }, + { + field: 'source.ip', + value: '192.168.1.1', + }, + ], + [ALERT_ORIGINAL_TIME]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_START]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_END]: '2020-10-28T05:00:02.000Z', + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, + }); + }); + + it('should not count documents that were covered by previous alerts', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['suppression-data']), + query: `host.name: *`, + alert_suppression: { + group_by: ['host.name', 'source.ip'], + }, + // The first invocation covers half of the source docs, the second invocation covers all documents. + // We will check and make sure the second invocation correctly filters out the first half that + // were alerted on by the first invocation. + from: 'now-2h', + interval: '1h', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 2, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + size: 1000, + sort: ['host.name', 'source.ip', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).to.eql(12); + + expect(previewAlerts[0]._source).to.eql({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: 'host-0', + }, + { + field: 'source.ip', + value: '192.168.1.1', + }, + ], + [ALERT_ORIGINAL_TIME]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_START]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_END]: '2020-10-28T05:00:02.000Z', + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, + }); + + expect(previewAlerts[1]._source).to.eql({ + ...previewAlerts[1]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: 'host-0', + }, + { + field: 'source.ip', + value: '192.168.1.1', + }, + ], + // Note: the timestamps here are 1 hour after the timestamps for previewAlerts[0] + [ALERT_ORIGINAL_TIME]: '2020-10-28T06:00:00.000Z', + [ALERT_SUPPRESSION_START]: '2020-10-28T06:00:00.000Z', + [ALERT_SUPPRESSION_END]: '2020-10-28T06:00:02.000Z', + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, + }); + }); + + // Only one source document populates destination.ip, but it populates the field with an array + // so we expect 2 groups to be created from the single document + it('should generate multiple alerts for a single doc in multiple groups', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['suppression-data']), + query: `destination.ip: *`, + alert_suppression: { + group_by: ['destination.ip'], + }, + from: 'now-1h', + interval: '1h', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T05:30:00.000Z'), + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + size: 1000, + sort: ['destination.ip'], + }); + expect(previewAlerts.length).to.eql(2); + + expect(previewAlerts[0]._source).to.eql({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'destination.ip', + value: '127.0.0.1', + }, + ], + [ALERT_ORIGINAL_TIME]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_START]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_END]: '2020-10-28T05:00:00.000Z', + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }); + }); + }); }); }; 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 index 48682e6b1e8b0b..2d9cdc01375465 100644 --- 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 @@ -20,10 +20,12 @@ export const getPreviewAlerts = async ({ es, previewId, size, + sort, }: { es: Client; previewId: string; size?: number; + sort?: string[]; }) => { const index = '.preview.alerts-security.alerts-*'; await refreshIndex(es, index); @@ -40,6 +42,7 @@ export const getPreviewAlerts = async ({ index, size, query, + sort, }); return result.hits.hits; }; 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 9e869a91bf0b15..39b8d2acf088e7 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 @@ -71,6 +71,7 @@ const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): RuleResponse => filters: undefined, saved_id: undefined, response_actions: undefined, + alert_suppression: undefined, }); /** diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts index 8b2a7f3aa2ddbe..6dcfcba1a66ba5 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts @@ -14,7 +14,7 @@ import { farequoteKQLSearchTestData, farequoteLuceneSearchTestData, sampleLogTestData, -} from './index_test_data'; +} from './index_test_data_random_sampler'; export default function ({ getPageObject, getService }: FtrProviderContext) { const headerPage = getPageObject('header'); @@ -62,7 +62,6 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { } await ml.dataVisualizerTable.assertSearchPanelExist(); - await ml.dataVisualizerTable.assertSampleSizeInputExists(); await ml.dataVisualizerTable.assertFieldTypeInputExists(); await ml.dataVisualizerTable.assertFieldNameInputExists(); @@ -113,18 +112,6 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { ); } - await ml.testExecution.logTestStep( - `${testData.suiteTitle} sample size control changes non-metric fields` - ); - for (const sampleSizeCase of testData.sampleSizeValidations) { - const { size, expected } = sampleSizeCase; - await ml.dataVisualizerTable.setSampleSizeInputValue( - size, - expected.field, - expected.docCountFormatted - ); - } - await ml.testExecution.logTestStep('sets and resets field type filter correctly'); await ml.dataVisualizerTable.setFieldTypeFilter( testData.fieldTypeFilters, diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_data_view_management.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_data_view_management.ts index 9378e36dc04f47..2b16939deadc67 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_data_view_management.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer_data_view_management.ts @@ -63,7 +63,7 @@ export default function ({ getService }: FtrProviderContext) { aggregatable: true, loading: false, exampleCount: 11, - docCountFormatted: '5000 (100%)', + docCountFormatted: '86,274 (100%)', viewableInLens: true, hasActionMenu: true, }, @@ -92,7 +92,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '86,274 (100%)', statsMaxDecimalPlaces: 3, topValuesCount: 11, viewableInLens: true, @@ -153,7 +153,6 @@ export default function ({ getService }: FtrProviderContext) { } await ml.dataVisualizerTable.assertSearchPanelExist(); - await ml.dataVisualizerTable.assertSampleSizeInputExists(); await ml.dataVisualizerTable.assertFieldTypeInputExists(); await ml.dataVisualizerTable.assertFieldNameInputExists(); diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_test_data.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_test_data.ts index 84531b7146eee5..6b285f05ad87a7 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_test_data.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_test_data.ts @@ -15,8 +15,8 @@ export const farequoteDataViewTestData: TestData = { fieldNameFilters: ['airline', '@timestamp'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.KEYWORD], sampleSizeValidations: [ - { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, - { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + { size: 1000, expected: { field: 'airline', docCountFormatted: '1,000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5,000 (100%)' } }, ], expected: { totalDocCountFormatted: '86,274', @@ -27,7 +27,7 @@ export const farequoteDataViewTestData: TestData = { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', statsMaxDecimalPlaces: 3, topValuesCount: 11, viewableInLens: true, @@ -40,7 +40,7 @@ export const farequoteDataViewTestData: TestData = { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', exampleCount: 2, viewableInLens: true, }, @@ -61,7 +61,7 @@ export const farequoteDataViewTestData: TestData = { aggregatable: true, loading: false, exampleCount: 1, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, { @@ -71,7 +71,7 @@ export const farequoteDataViewTestData: TestData = { aggregatable: true, loading: false, exampleCount: 11, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, { @@ -91,7 +91,7 @@ export const farequoteDataViewTestData: TestData = { aggregatable: true, loading: false, exampleCount: 1, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, ], @@ -112,8 +112,8 @@ export const farequoteKQLSearchTestData: TestData = { fieldNameFilters: ['@version'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.DATE, ML_JOB_FIELD_TYPES.TEXT], sampleSizeValidations: [ - { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, - { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + { size: 1000, expected: { field: 'airline', docCountFormatted: '1,000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5,000 (100%)' } }, ], expected: { totalDocCountFormatted: '34,415', @@ -124,7 +124,7 @@ export const farequoteKQLSearchTestData: TestData = { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', statsMaxDecimalPlaces: 3, topValuesCount: 11, viewableInLens: true, @@ -137,7 +137,7 @@ export const farequoteKQLSearchTestData: TestData = { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', exampleCount: 2, viewableInLens: true, }, @@ -158,7 +158,7 @@ export const farequoteKQLSearchTestData: TestData = { aggregatable: true, loading: false, exampleCount: 1, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, { @@ -168,7 +168,7 @@ export const farequoteKQLSearchTestData: TestData = { aggregatable: true, loading: false, exampleCount: 5, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, { @@ -188,7 +188,7 @@ export const farequoteKQLSearchTestData: TestData = { aggregatable: true, loading: false, exampleCount: 1, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, ], @@ -209,8 +209,8 @@ export const farequoteKQLFiltersSearchTestData: TestData = { fieldNameFilters: ['@version'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.DATE, ML_JOB_FIELD_TYPES.TEXT], sampleSizeValidations: [ - { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, - { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + { size: 1000, expected: { field: 'airline', docCountFormatted: '1,000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5,000 (100%)' } }, ], expected: { filters: [{ key: 'airline', value: 'ASA' }], @@ -222,7 +222,7 @@ export const farequoteKQLFiltersSearchTestData: TestData = { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', statsMaxDecimalPlaces: 3, topValuesCount: 11, viewableInLens: true, @@ -235,7 +235,7 @@ export const farequoteKQLFiltersSearchTestData: TestData = { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', exampleCount: 2, viewableInLens: true, }, @@ -256,7 +256,7 @@ export const farequoteKQLFiltersSearchTestData: TestData = { aggregatable: true, loading: false, exampleCount: 1, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, { @@ -267,7 +267,7 @@ export const farequoteKQLFiltersSearchTestData: TestData = { loading: false, exampleCount: 1, exampleContent: ['ASA'], - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, { @@ -287,7 +287,7 @@ export const farequoteKQLFiltersSearchTestData: TestData = { aggregatable: true, loading: false, exampleCount: 1, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, ], @@ -308,8 +308,8 @@ export const farequoteLuceneSearchTestData: TestData = { fieldNameFilters: ['@version.keyword', 'type'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.NUMBER], sampleSizeValidations: [ - { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, - { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + { size: 1000, expected: { field: 'airline', docCountFormatted: '1,000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5,000 (100%)' } }, ], expected: { totalDocCountFormatted: '34,416', @@ -320,7 +320,7 @@ export const farequoteLuceneSearchTestData: TestData = { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', statsMaxDecimalPlaces: 3, topValuesCount: 11, viewableInLens: true, @@ -333,7 +333,7 @@ export const farequoteLuceneSearchTestData: TestData = { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', exampleCount: 2, viewableInLens: true, }, @@ -354,7 +354,7 @@ export const farequoteLuceneSearchTestData: TestData = { aggregatable: true, loading: false, exampleCount: 1, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, { @@ -364,7 +364,7 @@ export const farequoteLuceneSearchTestData: TestData = { aggregatable: true, loading: false, exampleCount: 5, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, { @@ -384,7 +384,7 @@ export const farequoteLuceneSearchTestData: TestData = { aggregatable: true, loading: false, exampleCount: 1, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, ], @@ -405,8 +405,8 @@ export const farequoteLuceneFiltersSearchTestData: TestData = { fieldNameFilters: ['@version.keyword', 'type'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.NUMBER], sampleSizeValidations: [ - { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, - { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + { size: 1000, expected: { field: 'airline', docCountFormatted: '1,000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5,000 (100%)' } }, ], expected: { filters: [{ key: 'airline', value: 'ASA' }], @@ -418,7 +418,7 @@ export const farequoteLuceneFiltersSearchTestData: TestData = { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', statsMaxDecimalPlaces: 3, topValuesCount: 11, viewableInLens: true, @@ -431,7 +431,7 @@ export const farequoteLuceneFiltersSearchTestData: TestData = { existsInDocs: true, aggregatable: true, loading: false, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', exampleCount: 2, viewableInLens: true, }, @@ -452,7 +452,7 @@ export const farequoteLuceneFiltersSearchTestData: TestData = { aggregatable: true, loading: false, exampleCount: 1, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, { @@ -463,7 +463,7 @@ export const farequoteLuceneFiltersSearchTestData: TestData = { loading: false, exampleCount: 1, exampleContent: ['ASA'], - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, { @@ -483,7 +483,7 @@ export const farequoteLuceneFiltersSearchTestData: TestData = { aggregatable: true, loading: false, exampleCount: 1, - docCountFormatted: '5000 (100%)', + docCountFormatted: '5,000 (100%)', viewableInLens: true, }, ], diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_test_data_random_sampler.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_test_data_random_sampler.ts new file mode 100644 index 00000000000000..0b8fc95ceaa04d --- /dev/null +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_test_data_random_sampler.ts @@ -0,0 +1,535 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ML_JOB_FIELD_TYPES } from '@kbn/ml-plugin/common/constants/field_types'; +import { TestData } from './types'; + +export const farequoteDataViewTestData: TestData = { + suiteTitle: 'farequote index pattern', + isSavedSearch: false, + sourceIndexOrSavedSearch: 'ft_farequote', + fieldNameFilters: ['airline', '@timestamp'], + fieldTypeFilters: [ML_JOB_FIELD_TYPES.KEYWORD], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1,000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5,000 (100%)' } }, + ], + expected: { + totalDocCountFormatted: '86,274', + metricFields: [ + { + fieldName: 'responsetime', + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '86,274 (100%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 11, + viewableInLens: true, + }, + ], + nonMetricFields: [ + { + fieldName: '@timestamp', + type: ML_JOB_FIELD_TYPES.DATE, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '86,274 (100%)', + exampleCount: 2, + viewableInLens: true, + }, + { + fieldName: '@version', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '', + viewableInLens: false, + }, + { + fieldName: '@version.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '86,274 (100%)', + viewableInLens: true, + }, + { + fieldName: 'airline', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 11, + docCountFormatted: '86,274 (100%)', + viewableInLens: true, + }, + { + fieldName: 'type', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '', + viewableInLens: false, + }, + { + fieldName: 'type.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '86,274 (100%)', + viewableInLens: true, + }, + ], + emptyFields: ['sourcetype'], + visibleMetricFieldsCount: 1, + totalMetricFieldsCount: 1, + populatedFieldsCount: 7, + totalFieldsCount: 8, + fieldNameFiltersResultCount: 2, + fieldTypeFiltersResultCount: 3, + }, +}; + +export const farequoteKQLSearchTestData: TestData = { + suiteTitle: 'KQL saved search', + isSavedSearch: true, + sourceIndexOrSavedSearch: 'ft_farequote_kuery', + fieldNameFilters: ['@version'], + fieldTypeFilters: [ML_JOB_FIELD_TYPES.DATE, ML_JOB_FIELD_TYPES.TEXT], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1,000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5,000 (100%)' } }, + ], + expected: { + totalDocCountFormatted: '34,415', + metricFields: [ + { + fieldName: 'responsetime', + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '34,415 (100%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 11, + viewableInLens: true, + }, + ], + nonMetricFields: [ + { + fieldName: '@timestamp', + type: ML_JOB_FIELD_TYPES.DATE, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '34,415 (100%)', + exampleCount: 2, + viewableInLens: true, + }, + { + fieldName: '@version', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '', + viewableInLens: false, + }, + { + fieldName: '@version.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '34,415 (100%)', + viewableInLens: true, + }, + { + fieldName: 'airline', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 5, + docCountFormatted: '34,415 (100%)', + viewableInLens: true, + }, + { + fieldName: 'type', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '', + viewableInLens: false, + }, + { + fieldName: 'type.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '34,415 (100%)', + viewableInLens: true, + }, + ], + emptyFields: ['sourcetype'], + visibleMetricFieldsCount: 1, + totalMetricFieldsCount: 1, + populatedFieldsCount: 7, + totalFieldsCount: 8, + fieldNameFiltersResultCount: 1, + fieldTypeFiltersResultCount: 3, + }, +}; + +export const farequoteKQLFiltersSearchTestData: TestData = { + suiteTitle: 'KQL saved search and filters', + isSavedSearch: true, + sourceIndexOrSavedSearch: 'ft_farequote_filter_and_kuery', + fieldNameFilters: ['@version'], + fieldTypeFilters: [ML_JOB_FIELD_TYPES.DATE, ML_JOB_FIELD_TYPES.TEXT], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1,000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5,000 (100%)' } }, + ], + expected: { + filters: [{ key: 'airline', value: 'ASA' }], + totalDocCountFormatted: '5,674', + metricFields: [ + { + fieldName: 'responsetime', + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '5,674 (100%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 11, + viewableInLens: true, + }, + ], + nonMetricFields: [ + { + fieldName: '@timestamp', + type: ML_JOB_FIELD_TYPES.DATE, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '5,674 (100%)', + exampleCount: 2, + viewableInLens: true, + }, + { + fieldName: '@version', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '', + viewableInLens: false, + }, + { + fieldName: '@version.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '5,674 (100%)', + viewableInLens: true, + }, + { + fieldName: 'airline', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + exampleContent: ['ASA'], + docCountFormatted: '5,674 (100%)', + viewableInLens: true, + }, + { + fieldName: 'type', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '', + viewableInLens: false, + }, + { + fieldName: 'type.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '5,674 (100%)', + viewableInLens: true, + }, + ], + emptyFields: ['sourcetype'], + visibleMetricFieldsCount: 1, + totalMetricFieldsCount: 1, + populatedFieldsCount: 7, + totalFieldsCount: 8, + fieldNameFiltersResultCount: 1, + fieldTypeFiltersResultCount: 3, + }, +}; + +export const farequoteLuceneSearchTestData: TestData = { + suiteTitle: 'lucene saved search', + isSavedSearch: true, + sourceIndexOrSavedSearch: 'ft_farequote_lucene', + fieldNameFilters: ['@version.keyword', 'type'], + fieldTypeFilters: [ML_JOB_FIELD_TYPES.NUMBER], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1,000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5,000 (100%)' } }, + ], + expected: { + totalDocCountFormatted: '34,416', + metricFields: [ + { + fieldName: 'responsetime', + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '34,416 (100%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 11, + viewableInLens: true, + }, + ], + nonMetricFields: [ + { + fieldName: '@timestamp', + type: ML_JOB_FIELD_TYPES.DATE, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '34,416 (100%)', + exampleCount: 2, + viewableInLens: true, + }, + { + fieldName: '@version', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '', + viewableInLens: false, + }, + { + fieldName: '@version.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '34,416 (100%)', + viewableInLens: true, + }, + { + fieldName: 'airline', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 5, + docCountFormatted: '34,416 (100%)', + viewableInLens: true, + }, + { + fieldName: 'type', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '', + viewableInLens: false, + }, + { + fieldName: 'type.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '34,416 (100%)', + viewableInLens: true, + }, + ], + emptyFields: ['sourcetype'], + visibleMetricFieldsCount: 1, + totalMetricFieldsCount: 1, + populatedFieldsCount: 7, + totalFieldsCount: 8, + fieldNameFiltersResultCount: 2, + fieldTypeFiltersResultCount: 1, + }, +}; + +export const farequoteLuceneFiltersSearchTestData: TestData = { + suiteTitle: 'lucene saved search and filter', + isSavedSearch: true, + sourceIndexOrSavedSearch: 'ft_farequote_filter_and_lucene', + fieldNameFilters: ['@version.keyword', 'type'], + fieldTypeFilters: [ML_JOB_FIELD_TYPES.NUMBER], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1,000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5,000 (100%)' } }, + ], + expected: { + filters: [{ key: 'airline', value: 'ASA' }], + totalDocCountFormatted: '5,673', + metricFields: [ + { + fieldName: 'responsetime', + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '5,673 (100%)', + statsMaxDecimalPlaces: 3, + topValuesCount: 11, + viewableInLens: true, + }, + ], + nonMetricFields: [ + { + fieldName: '@timestamp', + type: ML_JOB_FIELD_TYPES.DATE, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '5,673 (100%)', + exampleCount: 2, + viewableInLens: true, + }, + { + fieldName: '@version', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '', + viewableInLens: false, + }, + { + fieldName: '@version.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '5,673 (100%)', + viewableInLens: true, + }, + { + fieldName: 'airline', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + exampleContent: ['ASA'], + docCountFormatted: '5,673 (100%)', + viewableInLens: true, + }, + { + fieldName: 'type', + type: ML_JOB_FIELD_TYPES.TEXT, + existsInDocs: true, + aggregatable: false, + loading: false, + exampleCount: 1, + docCountFormatted: '', + viewableInLens: false, + }, + { + fieldName: 'type.keyword', + type: ML_JOB_FIELD_TYPES.KEYWORD, + existsInDocs: true, + aggregatable: true, + loading: false, + exampleCount: 1, + docCountFormatted: '5,673 (100%)', + viewableInLens: true, + }, + ], + emptyFields: ['sourcetype'], + visibleMetricFieldsCount: 1, + totalMetricFieldsCount: 1, + populatedFieldsCount: 7, + totalFieldsCount: 8, + fieldNameFiltersResultCount: 2, + fieldTypeFiltersResultCount: 1, + }, +}; + +export const sampleLogTestData: TestData = { + suiteTitle: 'geo point field', + isSavedSearch: false, + sourceIndexOrSavedSearch: 'ft_module_sample_logs', + fieldNameFilters: ['geo.coordinates'], + fieldTypeFilters: [ML_JOB_FIELD_TYPES.GEO_POINT], + rowsPerPage: 50, + expected: { + totalDocCountFormatted: '408', + metricFields: [], + // only testing the geo_point fields + nonMetricFields: [ + { + fieldName: 'geo.coordinates', + type: ML_JOB_FIELD_TYPES.GEO_POINT, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '408 (100%)', + exampleCount: 10, + viewableInLens: false, + }, + ], + emptyFields: [], + visibleMetricFieldsCount: 4, + totalMetricFieldsCount: 5, + populatedFieldsCount: 35, + totalFieldsCount: 36, + fieldNameFiltersResultCount: 1, + fieldTypeFiltersResultCount: 1, + }, + sampleSizeValidations: [ + { size: 1000, expected: { field: 'geo.coordinates', docCountFormatted: '408 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '408 (100%)' } }, + ], +}; diff --git a/x-pack/test/functional/apps/spaces/spaces_selection.ts b/x-pack/test/functional/apps/spaces/spaces_selection.ts index 254a883ebf920d..9eb54eeb9c7e34 100644 --- a/x-pack/test/functional/apps/spaces/spaces_selection.ts +++ b/x-pack/test/functional/apps/spaces/spaces_selection.ts @@ -23,7 +23,7 @@ export default function spaceSelectorFunctionalTests({ const spacesService = getService('spaces'); // Failing: See https://github.com/elastic/kibana/issues/142155 - describe.skip('Spaces', function () { + describe('Spaces', function () { const testSpacesIds = ['another-space', ...Array.from('123456789', (idx) => `space-${idx}`)]; before(async () => { for (const testSpaceId of testSpacesIds) { diff --git a/x-pack/test/functional/es_archives/monitoring/kibana/rules_and_actions_package/data.json.gz b/x-pack/test/functional/es_archives/monitoring/kibana/rules_and_actions_package/data.json.gz new file mode 100644 index 00000000000000..dd9f54a477ec02 Binary files /dev/null and b/x-pack/test/functional/es_archives/monitoring/kibana/rules_and_actions_package/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/monitoring/kibana/rules_and_actions_package/mappings.json b/x-pack/test/functional/es_archives/monitoring/kibana/rules_and_actions_package/mappings.json new file mode 100644 index 00000000000000..5684702273ac75 --- /dev/null +++ b/x-pack/test/functional/es_archives/monitoring/kibana/rules_and_actions_package/mappings.json @@ -0,0 +1,7138 @@ +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.cluster_stats-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.cluster_stats-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.cluster_stats", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_state": { + "properties": { + "master_node": { + "path": "elasticsearch.cluster.stats.state.master_node", + "type": "alias" + }, + "nodes_hash": { + "path": "elasticsearch.cluster.stats.state.nodes_hash", + "type": "alias" + }, + "state_uuid": { + "path": "elasticsearch.cluster.stats.state.state_uuid", + "type": "alias" + }, + "status": { + "path": "elasticsearch.cluster.stats.status", + "type": "alias" + }, + "version": { + "path": "elasticsearch.cluster.stats.state.version", + "type": "alias" + } + } + }, + "cluster_stats": { + "properties": { + "indices": { + "properties": { + "count": { + "path": "elasticsearch.cluster.stats.indices.total", + "type": "alias" + }, + "shards": { + "properties": { + "total": { + "path": "elasticsearch.cluster.stats.indices.shards.count", + "type": "alias" + } + } + } + } + }, + "nodes": { + "properties": { + "count": { + "properties": { + "total": { + "path": "elasticsearch.cluster.stats.nodes.count", + "type": "alias" + } + } + }, + "jvm": { + "properties": { + "max_uptime_in_millis": { + "path": "elasticsearch.cluster.stats.nodes.jvm.max_uptime.ms", + "type": "alias" + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "path": "elasticsearch.cluster.stats.nodes.jvm.memory.heap.max.bytes", + "type": "alias" + }, + "heap_used_in_bytes": { + "path": "elasticsearch.cluster.stats.nodes.jvm.memory.heap.used.bytes", + "type": "alias" + } + } + } + } + } + } + } + } + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stats": { + "properties": { + "indices": { + "properties": { + "docs": { + "properties": { + "total": { + "type": "long" + } + } + }, + "fielddata": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "shards": { + "properties": { + "count": { + "type": "long" + }, + "primaries": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "total": { + "type": "long" + } + } + }, + "license": { + "properties": { + "cluster_needs_tls": { + "type": "boolean" + }, + "expiry_date": { + "type": "date" + }, + "expiry_date_in_millis": { + "type": "long" + }, + "issue_date": { + "type": "date" + }, + "issue_date_in_millis": { + "type": "long" + }, + "issued_to": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "max_nodes": { + "type": "long" + }, + "start_date_in_millis": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "nodes": { + "properties": { + "count": { + "type": "long" + }, + "data": { + "type": "long" + }, + "fs": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "jvm": { + "properties": { + "max_uptime": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "memory": { + "properties": { + "heap": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "master": { + "type": "long" + }, + "stats": { + "properties": { + "data": { + "type": "long" + } + } + }, + "versions": { + "type": "text" + } + } + }, + "stack": { + "properties": { + "apm": { + "properties": { + "found": { + "type": "boolean" + } + } + }, + "xpack": { + "properties": { + "ccr": { + "properties": { + "available": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + } + } + } + } + } + } + }, + "state": { + "properties": { + "master_node": { + "ignore_above": 1024, + "type": "keyword" + }, + "nodes": { + "type": "flattened" + }, + "nodes_hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "license": { + "properties": { + "status": { + "path": "elasticsearch.cluster.stats.license.status", + "type": "alias" + }, + "type": { + "path": "elasticsearch.cluster.stats.license.type", + "type": "alias" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "stack_stats": { + "properties": { + "apm": { + "properties": { + "found": { + "path": "elasticsearch.cluster.stats.stack.apm.found", + "type": "alias" + } + } + }, + "xpack": { + "properties": { + "ccr": { + "properties": { + "available": { + "path": "elasticsearch.cluster.stats.stack.xpack.ccr.available", + "type": "alias" + }, + "enabled": { + "path": "elasticsearch.cluster.stats.stack.xpack.ccr.enabled", + "type": "alias" + } + } + } + } + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.cluster.stats.version", + "elasticsearch.cluster.stats.state.nodes_hash", + "elasticsearch.cluster.stats.state.master_node", + "elasticsearch.cluster.stats.state.version", + "elasticsearch.cluster.stats.state.state_uuid", + "elasticsearch.cluster.stats.status", + "elasticsearch.cluster.stats.nodes.versions", + "elasticsearch.cluster.stats.license.issued_to", + "elasticsearch.cluster.stats.license.issuer", + "elasticsearch.cluster.stats.license.status", + "elasticsearch.cluster.stats.license.type", + "elasticsearch.cluster.stats.license.uid", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.version", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.enrich-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.enrich-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.enrich", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "enrich": { + "properties": { + "executed_searches": { + "properties": { + "total": { + "type": "long" + } + } + }, + "executing_policy": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "task": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "cancellable": { + "type": "boolean" + }, + "id": { + "type": "long" + }, + "parent_task_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "task": { + "ignore_above": 1024, + "type": "keyword" + }, + "time": { + "properties": { + "running": { + "properties": { + "nano": { + "type": "long" + } + } + }, + "start": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "queue": { + "properties": { + "size": { + "type": "long" + } + } + }, + "remote_requests": { + "properties": { + "current": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.enrich.executing_policy.name", + "elasticsearch.enrich.executing_policy.task.task", + "elasticsearch.enrich.executing_policy.task.action", + "elasticsearch.enrich.executing_policy.task.parent_task_id", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.index-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.index-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.index", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "index": { + "properties": { + "created": { + "type": "long" + }, + "hidden": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "primaries": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + }, + "throttle_time_in_millis": { + "type": "long" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "type": "long" + } + } + }, + "query_cache": { + "properties": { + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "refresh": { + "properties": { + "external_total_time_in_millis": { + "type": "long" + }, + "total_time_in_millis": { + "type": "long" + } + } + }, + "request_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "doc_values_memory_in_bytes": { + "type": "long" + }, + "fixed_bit_set_memory_in_bytes": { + "type": "long" + }, + "index_writer_memory_in_bytes": { + "type": "long" + }, + "memory_in_bytes": { + "type": "long" + }, + "norms_memory_in_bytes": { + "type": "long" + }, + "points_memory_in_bytes": { + "type": "long" + }, + "stored_fields_memory_in_bytes": { + "type": "long" + }, + "term_vectors_memory_in_bytes": { + "type": "long" + }, + "terms_memory_in_bytes": { + "type": "long" + }, + "version_map_memory_in_bytes": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + }, + "shards": { + "properties": { + "primaries": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "properties": { + "bulk": { + "properties": { + "avg_size_in_bytes": { + "type": "long" + }, + "avg_time_in_millis": { + "type": "long" + }, + "total_operations": { + "type": "long" + }, + "total_size_in_bytes": { + "type": "long" + }, + "total_time_in_millis": { + "type": "long" + } + } + }, + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "fielddata": { + "properties": { + "evictions": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + }, + "throttle_time_in_millis": { + "type": "long" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "type": "long" + } + } + }, + "query_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "refresh": { + "properties": { + "external_total_time_in_millis": { + "type": "long" + }, + "total_time_in_millis": { + "type": "long" + } + } + }, + "request_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "doc_values_memory_in_bytes": { + "type": "long" + }, + "fixed_bit_set_memory_in_bytes": { + "type": "long" + }, + "index_writer_memory_in_bytes": { + "type": "long" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "memory_in_bytes": { + "type": "long" + }, + "norms_memory_in_bytes": { + "type": "long" + }, + "points_memory_in_bytes": { + "type": "long" + }, + "stored_fields_memory_in_bytes": { + "type": "long" + }, + "term_vectors_memory_in_bytes": { + "type": "long" + }, + "terms_memory_in_bytes": { + "type": "long" + }, + "version_map_memory_in_bytes": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "size_in_bytes": { + "type": "long" + } + } + } + } + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "index_stats": { + "properties": { + "index": { + "path": "elasticsearch.index.name", + "type": "alias" + }, + "primaries": { + "properties": { + "docs": { + "properties": { + "count": { + "path": "elasticsearch.index.primaries.docs.count", + "type": "alias" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "path": "elasticsearch.index.primaries.indexing.index_time_in_millis", + "type": "alias" + }, + "index_total": { + "path": "elasticsearch.index.primaries.indexing.index_total", + "type": "alias" + }, + "throttle_time_in_millis": { + "path": "elasticsearch.index.primaries.indexing.throttle_time_in_millis", + "type": "alias" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "path": "elasticsearch.index.primaries.merges.total_size_in_bytes", + "type": "alias" + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { + "path": "elasticsearch.index.primaries.refresh.total_time_in_millis", + "type": "alias" + } + } + }, + "segments": { + "properties": { + "count": { + "path": "elasticsearch.index.primaries.segments.count", + "type": "alias" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "path": "elasticsearch.index.primaries.store.size_in_bytes", + "type": "alias" + } + } + } + } + }, + "total": { + "properties": { + "fielddata": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.index.total.fielddata.memory_size_in_bytes", + "type": "alias" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "path": "elasticsearch.index.total.indexing.index_time_in_millis", + "type": "alias" + }, + "index_total": { + "path": "elasticsearch.index.total.indexing.index_total", + "type": "alias" + }, + "throttle_time_in_millis": { + "path": "elasticsearch.index.total.indexing.throttle_time_in_millis", + "type": "alias" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "path": "elasticsearch.index.total.merges.total_size_in_bytes", + "type": "alias" + } + } + }, + "query_cache": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.index.total.query_cache.memory_size_in_bytes", + "type": "alias" + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { + "path": "elasticsearch.index.total.refresh.total_time_in_millis", + "type": "alias" + } + } + }, + "request_cache": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.index.total.request_cache.memory_size_in_bytes", + "type": "alias" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "path": "elasticsearch.index.total.search.query_time_in_millis", + "type": "alias" + }, + "query_total": { + "path": "elasticsearch.index.total.search.query_total", + "type": "alias" + } + } + }, + "segments": { + "properties": { + "count": { + "path": "elasticsearch.index.total.segments.count", + "type": "alias" + }, + "doc_values_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.doc_values_memory_in_bytes", + "type": "alias" + }, + "fixed_bit_set_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.fixed_bit_set_memory_in_bytes", + "type": "alias" + }, + "index_writer_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.index_writer_memory_in_bytes", + "type": "alias" + }, + "memory_in_bytes": { + "path": "elasticsearch.index.total.segments.memory_in_bytes", + "type": "alias" + }, + "norms_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.norms_memory_in_bytes", + "type": "alias" + }, + "points_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.points_memory_in_bytes", + "type": "alias" + }, + "stored_fields_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.stored_fields_memory_in_bytes", + "type": "alias" + }, + "term_vectors_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.term_vectors_memory_in_bytes", + "type": "alias" + }, + "terms_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.terms_memory_in_bytes", + "type": "alias" + }, + "version_map_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.version_map_memory_in_bytes", + "type": "alias" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "path": "elasticsearch.index.total.store.size_in_bytes", + "type": "alias" + } + } + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.index.uuid", + "elasticsearch.index.status", + "elasticsearch.index.name", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.index_recovery-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.index_recovery-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.index_recovery", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "index": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "recovery": { + "properties": { + "id": { + "type": "long" + }, + "index": { + "properties": { + "files": { + "properties": { + "percent": { + "ignore_above": 1024, + "type": "keyword" + }, + "recovered": { + "type": "long" + }, + "reused": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "size": { + "properties": { + "recovered_in_bytes": { + "type": "long" + }, + "reused_in_bytes": { + "type": "long" + }, + "total_in_bytes": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "primary": { + "type": "boolean" + }, + "source": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport_address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stage": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "stop_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "target": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport_address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "total_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "translog": { + "properties": { + "percent": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "type": "long" + }, + "total_on_start": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "verify_index": { + "properties": { + "check_index_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "total_time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "index_recovery": { + "properties": { + "shards": { + "properties": { + "start_time_in_millis": { + "path": "elasticsearch.index.recovery.start_time.ms", + "type": "alias" + }, + "stop_time_in_millis": { + "path": "elasticsearch.index.recovery.stop_time.ms", + "type": "alias" + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.index.name", + "elasticsearch.index.recovery.index.files.percent", + "elasticsearch.index.recovery.name", + "elasticsearch.index.recovery.type", + "elasticsearch.index.recovery.stage", + "elasticsearch.index.recovery.translog.percent", + "elasticsearch.index.recovery.target.transport_address", + "elasticsearch.index.recovery.target.id", + "elasticsearch.index.recovery.target.host", + "elasticsearch.index.recovery.target.name", + "elasticsearch.index.recovery.source.transport_address", + "elasticsearch.index.recovery.source.id", + "elasticsearch.index.recovery.source.host", + "elasticsearch.index.recovery.source.name", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.index_summary-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.index_summary-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.index_summary", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "index": { + "properties": { + "summary": { + "properties": { + "primaries": { + "properties": { + "bulk": { + "properties": { + "operations": { + "properties": { + "count": { + "type": "long" + } + } + }, + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "time": { + "properties": { + "avg": { + "properties": { + "bytes": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + }, + "count": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "search": { + "properties": { + "query": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "total": { + "properties": { + "bulk": { + "properties": { + "operations": { + "properties": { + "count": { + "type": "long" + } + } + }, + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "time": { + "properties": { + "avg": { + "properties": { + "bytes": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "is_throttled": { + "type": "boolean" + }, + "throttle_time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "search": { + "properties": { + "query": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "indices_stats": { + "properties": { + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "path": "elasticsearch.index.summary.primaries.indexing.index.time.ms", + "type": "alias" + }, + "index_total": { + "path": "elasticsearch.index.summary.primaries.indexing.index.count", + "type": "alias" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "path": "elasticsearch.index.summary.total.indexing.index.count", + "type": "alias" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "path": "elasticsearch.index.summary.total.search.query.time.ms", + "type": "alias" + }, + "query_total": { + "path": "elasticsearch.index.summary.total.search.query.count", + "type": "alias" + } + } + } + } + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.node-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.node-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.node", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "jvm": { + "properties": { + "memory": { + "properties": { + "heap": { + "properties": { + "init": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "nonheap": { + "properties": { + "init": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "process": { + "properties": { + "mlockall": { + "type": "boolean" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.node.version", + "elasticsearch.node.jvm.version", + "elasticsearch.node.id", + "elasticsearch.node.name", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.node_stats-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.node_stats-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.node_stats", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "fs": { + "properties": { + "io_stats": { + "properties": { + "total": { + "properties": { + "operations": { + "properties": { + "count": { + "type": "long" + } + } + }, + "read": { + "properties": { + "operations": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "write": { + "properties": { + "operations": { + "properties": { + "count": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "summary": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "free": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + }, + "total_in_bytes": { + "type": "long" + } + } + } + } + }, + "indexing_pressure": { + "properties": { + "memory": { + "properties": { + "current": { + "properties": { + "all": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "combined_coordinating_and_primary": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "coordinating": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "primary": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "replica": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "limit_in_bytes": { + "type": "long" + }, + "total": { + "properties": { + "all": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "combined_coordinating_and_primary": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "coordinating": { + "properties": { + "bytes": { + "type": "long" + }, + "rejections": { + "type": "long" + } + } + }, + "primary": { + "properties": { + "bytes": { + "type": "long" + }, + "rejections": { + "type": "long" + } + } + }, + "replica": { + "properties": { + "bytes": { + "type": "long" + }, + "rejections": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "indices": { + "properties": { + "bulk": { + "properties": { + "avg_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "avg_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "operations": { + "properties": { + "total": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "total_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total_time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "fielddata": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "indexing": { + "properties": { + "index_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "index_total": { + "properties": { + "count": { + "type": "long" + } + } + }, + "throttle_time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "query_cache": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "request_cache": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "search": { + "properties": { + "query_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "query_total": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "doc_values": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "fixed_bit_set": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "index_writer": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "norms": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "points": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "stored_fields": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "term_vectors": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "terms": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "version_map": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "ingest": { + "properties": { + "total": { + "properties": { + "count": { + "type": "long" + }, + "current": { + "type": "long" + }, + "failed": { + "type": "long" + }, + "time_in_millis": { + "type": "long" + } + } + } + } + }, + "jvm": { + "properties": { + "gc": { + "properties": { + "collectors": { + "properties": { + "old": { + "properties": { + "collection": { + "properties": { + "count": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + } + } + }, + "young": { + "properties": { + "collection": { + "properties": { + "count": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "mem": { + "properties": { + "heap": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "pct": { + "type": "double" + } + } + } + } + }, + "pools": { + "properties": { + "old": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak_max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "survivor": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak_max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "young": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak_max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + } + } + }, + "os": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs": { + "properties": { + "quota": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "stat": { + "properties": { + "elapsed_periods": { + "properties": { + "count": { + "type": "long" + } + } + }, + "time_throttled": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "times_throttled": { + "properties": { + "count": { + "type": "long" + } + } + } + } + } + } + }, + "cpuacct": { + "properties": { + "usage": { + "properties": { + "ns": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "control_group": { + "ignore_above": 1024, + "type": "keyword" + }, + "limit": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "usage": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "cpu": { + "properties": { + "load_avg": { + "properties": { + "1m": { + "type": "half_float" + } + } + } + } + } + } + }, + "process": { + "properties": { + "cpu": { + "properties": { + "pct": { + "type": "double" + } + } + } + } + }, + "thread_pool": { + "properties": { + "bulk": { + "properties": { + "queue": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "get": { + "properties": { + "queue": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "index": { + "properties": { + "queue": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "search": { + "properties": { + "queue": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "write": { + "properties": { + "queue": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node_stats": { + "properties": { + "fs": { + "properties": { + "io_stats": { + "properties": { + "total": { + "properties": { + "operations": { + "path": "elasticsearch.node.stats.fs.io_stats.total.operations.count", + "type": "alias" + }, + "read_operations": { + "path": "elasticsearch.node.stats.fs.io_stats.total.read.operations.count", + "type": "alias" + }, + "write_operations": { + "path": "elasticsearch.node.stats.fs.io_stats.total.write.operations.count", + "type": "alias" + } + } + } + } + }, + "summary": { + "properties": { + "available": { + "properties": { + "bytes": { + "path": "elasticsearch.node.stats.fs.summary.available.bytes", + "type": "alias" + } + } + }, + "total": { + "properties": { + "bytes": { + "path": "elasticsearch.node.stats.fs.summary.total.bytes", + "type": "alias" + } + } + } + } + }, + "total": { + "properties": { + "available_in_bytes": { + "path": "elasticsearch.node.stats.fs.summary.available.bytes", + "type": "alias" + }, + "total_in_bytes": { + "path": "elasticsearch.node.stats.fs.summary.total.bytes", + "type": "alias" + } + } + } + } + }, + "indices": { + "properties": { + "docs": { + "properties": { + "count": { + "path": "elasticsearch.node.stats.indices.docs.count", + "type": "alias" + } + } + }, + "fielddata": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.node.stats.indices.fielddata.memory.bytes", + "type": "alias" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "path": "elasticsearch.node.stats.indices.indexing.index_time.ms", + "type": "alias" + }, + "index_total": { + "path": "elasticsearch.node.stats.indices.indexing.index_total.count", + "type": "alias" + }, + "throttle_time_in_millis": { + "path": "elasticsearch.node.stats.indices.indexing.throttle_time.ms", + "type": "alias" + } + } + }, + "query_cache": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.node.stats.indices.query_cache.memory.bytes", + "type": "alias" + } + } + }, + "request_cache": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.node.stats.indices.request_cache.memory.bytes", + "type": "alias" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "path": "elasticsearch.node.stats.indices.search.query_time.ms", + "type": "alias" + }, + "query_total": { + "path": "elasticsearch.node.stats.indices.search.query_total.count", + "type": "alias" + } + } + }, + "segments": { + "properties": { + "count": { + "path": "elasticsearch.node.stats.indices.segments.count", + "type": "alias" + }, + "doc_values_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.doc_values.memory.bytes", + "type": "alias" + }, + "fixed_bit_set_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.fixed_bit_set.memory.bytes", + "type": "alias" + }, + "index_writer_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.index_writer.memory.bytes", + "type": "alias" + }, + "memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.memory.bytes", + "type": "alias" + }, + "norms_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.norms.memory.bytes", + "type": "alias" + }, + "points_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.points.memory.bytes", + "type": "alias" + }, + "stored_fields_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.stored_fields.memory.bytes", + "type": "alias" + }, + "term_vectors_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.term_vectors.memory.bytes", + "type": "alias" + }, + "terms_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.terms.memory.bytes", + "type": "alias" + }, + "version_map_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.version_map.memory.bytes", + "type": "alias" + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "path": "elasticsearch.node.stats.indices.store.size.bytes", + "type": "alias" + } + } + }, + "size_in_bytes": { + "path": "elasticsearch.node.stats.indices.store.size.bytes", + "type": "alias" + } + } + } + } + }, + "jvm": { + "properties": { + "gc": { + "properties": { + "collectors": { + "properties": { + "old": { + "properties": { + "collection_count": { + "path": "elasticsearch.node.stats.jvm.gc.collectors.old.collection.count", + "type": "alias" + }, + "collection_time_in_millis": { + "path": "elasticsearch.node.stats.jvm.gc.collectors.old.collection.ms", + "type": "alias" + } + } + }, + "young": { + "properties": { + "collection_count": { + "path": "elasticsearch.node.stats.jvm.gc.collectors.young.collection.count", + "type": "alias" + }, + "collection_time_in_millis": { + "path": "elasticsearch.node.stats.jvm.gc.collectors.young.collection.ms", + "type": "alias" + } + } + } + } + } + } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "path": "elasticsearch.node.stats.jvm.mem.heap.max.bytes", + "type": "alias" + }, + "heap_used_in_bytes": { + "path": "elasticsearch.node.stats.jvm.mem.heap.used.bytes", + "type": "alias" + }, + "heap_used_percent": { + "path": "elasticsearch.node.stats.jvm.mem.heap.used.pct", + "type": "alias" + } + } + } + } + }, + "node_id": { + "path": "elasticsearch.node.id", + "type": "alias" + }, + "os": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs_quota_micros": { + "path": "elasticsearch.node.stats.os.cgroup.cpu.cfs.quota.us", + "type": "alias" + }, + "stat": { + "properties": { + "number_of_elapsed_periods": { + "path": "elasticsearch.node.stats.os.cgroup.cpu.stat.elapsed_periods.count", + "type": "alias" + }, + "number_of_times_throttled": { + "path": "elasticsearch.node.stats.os.cgroup.cpu.stat.times_throttled.count", + "type": "alias" + }, + "time_throttled_nanos": { + "path": "elasticsearch.node.stats.os.cgroup.cpu.stat.time_throttled.ns", + "type": "alias" + } + } + } + } + }, + "cpuacct": { + "properties": { + "usage_nanos": { + "path": "elasticsearch.node.stats.os.cgroup.cpuacct.usage.ns", + "type": "alias" + } + } + }, + "memory": { + "properties": { + "control_group": { + "path": "elasticsearch.node.stats.os.cgroup.memory.control_group", + "type": "alias" + }, + "limit_in_bytes": { + "path": "elasticsearch.node.stats.os.cgroup.memory.limit.bytes", + "type": "alias" + }, + "usage_in_bytes": { + "path": "elasticsearch.node.stats.os.cgroup.memory.usage.bytes", + "type": "alias" + } + } + } + } + }, + "cpu": { + "properties": { + "load_average": { + "properties": { + "1m": { + "path": "elasticsearch.node.stats.os.cpu.load_avg.1m", + "type": "alias" + } + } + } + } + } + } + }, + "process": { + "properties": { + "cpu": { + "properties": { + "percent": { + "path": "elasticsearch.node.stats.process.cpu.pct", + "type": "alias" + } + } + } + } + }, + "thread_pool": { + "properties": { + "bulk": { + "properties": { + "queue": { + "path": "elasticsearch.node.stats.thread_pool.bulk.queue.count", + "type": "alias" + }, + "rejected": { + "path": "elasticsearch.node.stats.thread_pool.bulk.rejected.count", + "type": "alias" + } + } + }, + "get": { + "properties": { + "queue": { + "path": "elasticsearch.node.stats.thread_pool.get.queue.count", + "type": "alias" + }, + "rejected": { + "path": "elasticsearch.node.stats.thread_pool.get.rejected.count", + "type": "alias" + } + } + }, + "index": { + "properties": { + "queue": { + "path": "elasticsearch.node.stats.thread_pool.index.queue.count", + "type": "alias" + }, + "rejected": { + "path": "elasticsearch.node.stats.thread_pool.index.rejected.count", + "type": "alias" + } + } + }, + "search": { + "properties": { + "queue": { + "path": "elasticsearch.node.stats.thread_pool.search.queue.count", + "type": "alias" + }, + "rejected": { + "path": "elasticsearch.node.stats.thread_pool.search.rejected.count", + "type": "alias" + } + } + }, + "write": { + "properties": { + "queue": { + "path": "elasticsearch.node.stats.thread_pool.write.queue.count", + "type": "alias" + }, + "rejected": { + "path": "elasticsearch.node.stats.thread_pool.write.rejected.count", + "type": "alias" + } + } + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.node.stats.os.cgroup.memory.control_group", + "elasticsearch.node.stats.os.cgroup.memory.limit.bytes", + "elasticsearch.node.stats.os.cgroup.memory.usage.bytes", + "elasticsearch.node.id", + "elasticsearch.node.name", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.shard-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.shard-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.shard", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stats": { + "properties": { + "state": { + "properties": { + "state_uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "index": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "shard": { + "properties": { + "number": { + "type": "long" + }, + "primary": { + "type": "boolean" + }, + "relocating_node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "shard": { + "properties": { + "index": { + "path": "elasticsearch.index.name", + "type": "alias" + }, + "node": { + "path": "elasticsearch.node.id", + "type": "alias" + }, + "primary": { + "path": "elasticsearch.shard.primary", + "type": "alias" + }, + "shard": { + "path": "elasticsearch.shard.number", + "type": "alias" + }, + "state": { + "path": "elasticsearch.shard.state", + "type": "alias" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.shard.state", + "elasticsearch.shard.relocating_node.name", + "elasticsearch.shard.relocating_node.id", + "elasticsearch.shard.source_node.name", + "elasticsearch.shard.source_node.uuid", + "elasticsearch.index.name", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.cluster.stats.state.state_uuid", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.cluster_actions-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.cluster_actions-*" + ], + "name": "metrics-kibana.stack_monitoring.cluster_actions", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "kibana.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "cluster_actions": { + "properties": { + "overdue": { + "properties": { + "count": { + "type": "long" + }, + "delay": { + "properties": { + "p50": { + "type": "float" + }, + "p99": { + "type": "float" + } + } + } + } + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "kibana_stats": { + "properties": { + "kibana": { + "properties": { + "uuid": { + "path": "service.id", + "type": "alias" + }, + "version": { + "path": "service.version", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.address", + "service.version", + "service.type", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "kibana.elasticsearch.cluster.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.cluster_rules-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.cluster_rules-*" + ], + "name": "metrics-kibana.stack_monitoring.cluster_rules", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "kibana.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "cluster_rules": { + "properties": { + "overdue": { + "properties": { + "count": { + "type": "long" + }, + "delay": { + "properties": { + "p50": { + "type": "float" + }, + "p99": { + "type": "float" + } + } + } + } + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "kibana_stats": { + "properties": { + "kibana": { + "properties": { + "uuid": { + "path": "service.id", + "type": "alias" + }, + "version": { + "path": "service.version", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.address", + "service.version", + "service.type", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "kibana.elasticsearch.cluster.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.node_actions-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.node_actions-*" + ], + "name": "metrics-kibana.stack_monitoring.node_actions", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "kibana.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node_actions": { + "properties": { + "executions": { + "type": "long" + }, + "failures": { + "type": "long" + }, + "timeouts": { + "type": "long" + } + } + } + } + }, + "kibana_stats": { + "properties": { + "kibana": { + "properties": { + "uuid": { + "path": "service.id", + "type": "alias" + }, + "version": { + "path": "service.version", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.address", + "service.version", + "service.type", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "kibana.elasticsearch.cluster.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.node_rules-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.node_rules-*" + ], + "name": "metrics-kibana.stack_monitoring.node_rules", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "kibana.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node_rules": { + "properties": { + "executions": { + "type": "long" + }, + "failures": { + "type": "long" + }, + "timeouts": { + "type": "long" + } + } + } + } + }, + "kibana_stats": { + "properties": { + "kibana": { + "properties": { + "uuid": { + "path": "service.id", + "type": "alias" + }, + "version": { + "path": "service.version", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.address", + "service.version", + "service.type", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "kibana.elasticsearch.cluster.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.stats-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.stats-*" + ], + "name": "metrics-kibana.stack_monitoring.stats", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "kibana.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "stats": { + "properties": { + "concurrent_connections": { + "type": "long" + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "index": { + "ignore_above": 1024, + "type": "keyword" + }, + "kibana": { + "properties": { + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "distro": { + "ignore_above": 1024, + "type": "keyword" + }, + "distroRelease": { + "ignore_above": 1024, + "type": "keyword" + }, + "load": { + "properties": { + "15m": { + "type": "half_float" + }, + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + } + } + }, + "memory": { + "properties": { + "free_in_bytes": { + "type": "long" + }, + "total_in_bytes": { + "type": "long" + }, + "used_in_bytes": { + "type": "long" + } + } + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "platformRelease": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "event_loop_delay": { + "properties": { + "ms": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "memory": { + "properties": { + "heap": { + "properties": { + "size_limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "uptime": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "resident_set_size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "uptime": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "request": { + "properties": { + "disconnects": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "response_time": { + "properties": { + "avg": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "max": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "snapshot": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "usage": { + "properties": { + "index": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "kibana_stats": { + "properties": { + "concurrent_connections": { + "path": "kibana.stats.concurrent_connections", + "type": "alias" + }, + "kibana": { + "properties": { + "response_time": { + "properties": { + "max": { + "path": "kibana.stats.response_time.max.ms", + "type": "alias" + } + } + }, + "status": { + "path": "kibana.stats.status", + "type": "alias" + }, + "uuid": { + "path": "service.id", + "type": "alias" + }, + "version": { + "path": "service.version", + "type": "alias" + } + } + }, + "os": { + "properties": { + "load": { + "properties": { + "15m": { + "path": "kibana.stats.os.load.15m", + "type": "alias" + }, + "1m": { + "path": "kibana.stats.os.load.1m", + "type": "alias" + }, + "5m": { + "path": "kibana.stats.os.load.5m", + "type": "alias" + } + } + }, + "memory": { + "properties": { + "free_in_bytes": { + "path": "kibana.stats.os.memory.free_in_bytes", + "type": "alias" + } + } + } + } + }, + "process": { + "properties": { + "event_loop_delay": { + "path": "kibana.stats.process.event_loop_delay.ms", + "type": "alias" + }, + "memory": { + "properties": { + "heap": { + "properties": { + "size_limit": { + "path": "kibana.stats.process.memory.heap.size_limit.bytes", + "type": "alias" + } + } + }, + "resident_set_size_in_bytes": { + "path": "kibana.stats.process.memory.resident_set_size.bytes", + "type": "alias" + } + } + }, + "uptime_in_millis": { + "path": "kibana.stats.process.uptime.ms", + "type": "alias" + } + } + }, + "requests": { + "properties": { + "disconnects": { + "path": "kibana.stats.request.disconnects", + "type": "alias" + }, + "total": { + "path": "kibana.stats.request.total", + "type": "alias" + } + } + }, + "response_times": { + "properties": { + "average": { + "path": "kibana.stats.response_time.avg.ms", + "type": "alias" + }, + "max": { + "path": "kibana.stats.response_time.max.ms", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.address", + "service.version", + "service.type", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "kibana.elasticsearch.cluster.id", + "kibana.stats.kibana.status", + "kibana.stats.usage.index", + "kibana.stats.name", + "kibana.stats.index", + "kibana.stats.host.name", + "kibana.stats.status", + "kibana.stats.transport_address", + "kibana.stats.os.distro", + "kibana.stats.os.distroRelease", + "kibana.stats.os.platform", + "kibana.stats.os.platformRelease" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.status-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.status-*" + ], + "name": "metrics-kibana.stack_monitoring.status", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + } + } + }, + "kibana": { + "properties": { + "status": { + "properties": { + "metrics": { + "properties": { + "concurrent_connections": { + "type": "long" + }, + "requests": { + "properties": { + "disconnects": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "properties": { + "overall": { + "properties": { + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.name", + "service.version", + "service.type", + "service.address", + "ecs.version", + "error.message", + "kibana.status.name", + "kibana.status.status.overall.state" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-logstash.stack_monitoring.node-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "logstash" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-logstash.stack_monitoring.node-*" + ], + "name": "metrics-logstash.stack_monitoring.node", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "logstash" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "logstash.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "logstash": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "jvm": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "properties": { + "pipeline": { + "properties": { + "batch_size": { + "type": "long" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "representation": { + "properties": { + "graph": { + "properties": { + "edges": { + "type": "object" + }, + "vertices": { + "type": "object" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "workers": { + "type": "long" + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "logstash_state": { + "properties": { + "pipeline": { + "properties": { + "hash": { + "path": "logstash.node.state.pipeline.hash", + "type": "alias" + }, + "id": { + "path": "logstash.node.state.pipeline.id", + "type": "alias" + } + } + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.hostname", + "service.id", + "service.type", + "service.version", + "service.address", + "service.name", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "logstash.cluster.id", + "logstash.elasticsearch.cluster.id", + "logstash.node.jvm.version", + "logstash.node.host", + "logstash.node.version", + "logstash.node.id", + "logstash.node.state.pipeline.id", + "logstash.node.state.pipeline.hash", + "logstash.node.state.pipeline.ephemeral_id", + "logstash.node.state.pipeline.representation.hash", + "logstash.node.state.pipeline.representation.type", + "logstash.node.state.pipeline.representation.version" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-logstash.stack_monitoring.node_stats-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "logstash" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-logstash.stack_monitoring.node_stats-*" + ], + "name": "metrics-logstash.stack_monitoring.node_stats", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "logstash" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "logstash.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "logstash": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "state": { + "properties": { + "pipeline": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "stats": { + "properties": { + "events": { + "properties": { + "duration_in_millis": { + "type": "long" + }, + "filtered": { + "type": "long" + }, + "in": { + "type": "long" + }, + "out": { + "type": "long" + } + } + }, + "jvm": { + "properties": { + "gc": { + "properties": { + "collectors": { + "properties": { + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + }, + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + } + } + } + } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "long" + } + } + }, + "uptime_in_millis": { + "type": "long" + } + } + }, + "logstash": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "http_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pipeline": { + "properties": { + "batch_size": { + "type": "long" + }, + "workers": { + "type": "long" + } + } + }, + "snapshot": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs_quota_micros": { + "type": "long" + }, + "control_group": { + "type": "text" + }, + "stat": { + "type": "object" + } + } + }, + "cpuacct": { + "type": "object" + } + } + }, + "cpu": { + "properties": { + "load_average": { + "properties": { + "15m": { + "type": "half_float" + }, + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + } + } + }, + "percent": { + "type": "double" + } + } + } + } + }, + "pipelines": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "events": { + "properties": { + "duration_in_millis": { + "type": "long" + }, + "filtered": { + "type": "long" + }, + "in": { + "type": "long" + }, + "out": { + "type": "long" + }, + "queue_push_duration_in_millis": { + "type": "long" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "queue": { + "properties": { + "events_count": { + "type": "long" + }, + "max_queue_size_in_bytes": { + "type": "long" + }, + "queue_size_in_bytes": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "reloads": { + "properties": { + "failures": { + "type": "long" + }, + "successes": { + "type": "long" + } + } + }, + "vertices": { + "properties": { + "duration_in_millis": { + "type": "long" + }, + "events_in": { + "type": "long" + }, + "events_out": { + "type": "long" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "long_counters": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "type": "long" + } + }, + "type": "nested" + }, + "pipeline_ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "queue_push_duration_in_millis": { + "type": "long" + } + }, + "type": "nested" + } + }, + "type": "nested" + }, + "process": { + "properties": { + "cpu": { + "properties": { + "percent": { + "type": "double" + } + } + }, + "max_file_descriptors": { + "type": "long" + }, + "open_file_descriptors": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "events_count": { + "type": "long" + } + } + }, + "reloads": { + "properties": { + "failures": { + "type": "long" + }, + "successes": { + "type": "long" + } + } + }, + "timestamp": { + "type": "date" + } + } + } + } + } + } + }, + "logstash_stats": { + "properties": { + "events": { + "properties": { + "duration_in_millis": { + "path": "logstash.node.stats.events.duration_in_millis", + "type": "alias" + }, + "in": { + "path": "logstash.node.stats.events.in", + "type": "alias" + }, + "out": { + "path": "logstash.node.stats.events.out", + "type": "alias" + } + } + }, + "jvm": { + "properties": { + "mem": { + "properties": { + "heap_max_in_bytes": { + "path": "logstash.node.stats.jvm.mem.heap_max_in_bytes", + "type": "alias" + }, + "heap_used_in_bytes": { + "path": "logstash.node.stats.jvm.mem.heap_used_in_bytes", + "type": "alias" + } + } + }, + "uptime_in_millis": { + "path": "logstash.node.stats.jvm.uptime_in_millis", + "type": "alias" + } + } + }, + "logstash": { + "properties": { + "uuid": { + "path": "logstash.node.stats.logstash.uuid", + "type": "alias" + }, + "version": { + "path": "logstash.node.stats.logstash.version", + "type": "alias" + } + } + }, + "os": { + "properties": { + "cgroup": { + "properties": { + "cpuacct": { + "type": "object" + } + } + }, + "cpu": { + "properties": { + "load_average": { + "properties": { + "15m": { + "path": "logstash.node.stats.os.cpu.load_average.15m", + "type": "alias" + }, + "1m": { + "path": "logstash.node.stats.os.cpu.load_average.1m", + "type": "alias" + }, + "5m": { + "path": "logstash.node.stats.os.cpu.load_average.5m", + "type": "alias" + } + } + }, + "stat": { + "type": "object" + } + } + } + } + }, + "pipelines": { + "type": "nested" + }, + "process": { + "properties": { + "cpu": { + "properties": { + "percent": { + "path": "logstash.node.stats.process.cpu.percent", + "type": "alias" + } + } + } + } + }, + "queue": { + "properties": { + "events_count": { + "path": "logstash.node.stats.queue.events_count", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.hostname", + "service.id", + "service.type", + "service.version", + "service.address", + "service.name", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "logstash.elasticsearch.cluster.id", + "logstash.node.state.pipeline.id", + "logstash.node.state.pipeline.hash", + "logstash.node.stats.logstash.uuid", + "logstash.node.stats.logstash.version", + "logstash.node.stats.logstash.ephemeral_id", + "logstash.node.stats.logstash.host", + "logstash.node.stats.logstash.http_address", + "logstash.node.stats.logstash.name", + "logstash.node.stats.logstash.status", + "logstash.node.stats.os.cgroup.cpuacct.control_group", + "logstash.node.stats.os.cgroup.cpu.control_group", + "logstash.node.stats.pipelines.id", + "logstash.node.stats.pipelines.hash", + "logstash.node.stats.pipelines.ephemeral_id", + "logstash.node.stats.pipelines.queue.type", + "logstash.node.stats.pipelines.vertices.id", + "logstash.node.stats.pipelines.vertices.long_counters.name", + "logstash.node.stats.pipelines.vertices.pipeline_ephemeral_id", + "logstash.cluster.id" + ] + } + } + } + } + } + } +} diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_package/data.json.gz b/x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_package/data.json.gz new file mode 100644 index 00000000000000..60fd270584cbf7 Binary files /dev/null and b/x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_package/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_package/mappings.json b/x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_package/mappings.json new file mode 100644 index 00000000000000..5684702273ac75 --- /dev/null +++ b/x-pack/test/functional/es_archives/monitoring/singlecluster_yellow_platinum_package/mappings.json @@ -0,0 +1,7138 @@ +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.cluster_stats-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.cluster_stats-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.cluster_stats", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_state": { + "properties": { + "master_node": { + "path": "elasticsearch.cluster.stats.state.master_node", + "type": "alias" + }, + "nodes_hash": { + "path": "elasticsearch.cluster.stats.state.nodes_hash", + "type": "alias" + }, + "state_uuid": { + "path": "elasticsearch.cluster.stats.state.state_uuid", + "type": "alias" + }, + "status": { + "path": "elasticsearch.cluster.stats.status", + "type": "alias" + }, + "version": { + "path": "elasticsearch.cluster.stats.state.version", + "type": "alias" + } + } + }, + "cluster_stats": { + "properties": { + "indices": { + "properties": { + "count": { + "path": "elasticsearch.cluster.stats.indices.total", + "type": "alias" + }, + "shards": { + "properties": { + "total": { + "path": "elasticsearch.cluster.stats.indices.shards.count", + "type": "alias" + } + } + } + } + }, + "nodes": { + "properties": { + "count": { + "properties": { + "total": { + "path": "elasticsearch.cluster.stats.nodes.count", + "type": "alias" + } + } + }, + "jvm": { + "properties": { + "max_uptime_in_millis": { + "path": "elasticsearch.cluster.stats.nodes.jvm.max_uptime.ms", + "type": "alias" + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "path": "elasticsearch.cluster.stats.nodes.jvm.memory.heap.max.bytes", + "type": "alias" + }, + "heap_used_in_bytes": { + "path": "elasticsearch.cluster.stats.nodes.jvm.memory.heap.used.bytes", + "type": "alias" + } + } + } + } + } + } + } + } + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stats": { + "properties": { + "indices": { + "properties": { + "docs": { + "properties": { + "total": { + "type": "long" + } + } + }, + "fielddata": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "shards": { + "properties": { + "count": { + "type": "long" + }, + "primaries": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "total": { + "type": "long" + } + } + }, + "license": { + "properties": { + "cluster_needs_tls": { + "type": "boolean" + }, + "expiry_date": { + "type": "date" + }, + "expiry_date_in_millis": { + "type": "long" + }, + "issue_date": { + "type": "date" + }, + "issue_date_in_millis": { + "type": "long" + }, + "issued_to": { + "ignore_above": 1024, + "type": "keyword" + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "max_nodes": { + "type": "long" + }, + "start_date_in_millis": { + "type": "long" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "nodes": { + "properties": { + "count": { + "type": "long" + }, + "data": { + "type": "long" + }, + "fs": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "jvm": { + "properties": { + "max_uptime": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "memory": { + "properties": { + "heap": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "master": { + "type": "long" + }, + "stats": { + "properties": { + "data": { + "type": "long" + } + } + }, + "versions": { + "type": "text" + } + } + }, + "stack": { + "properties": { + "apm": { + "properties": { + "found": { + "type": "boolean" + } + } + }, + "xpack": { + "properties": { + "ccr": { + "properties": { + "available": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + } + } + } + } + } + } + }, + "state": { + "properties": { + "master_node": { + "ignore_above": 1024, + "type": "keyword" + }, + "nodes": { + "type": "flattened" + }, + "nodes_hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "state_uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "license": { + "properties": { + "status": { + "path": "elasticsearch.cluster.stats.license.status", + "type": "alias" + }, + "type": { + "path": "elasticsearch.cluster.stats.license.type", + "type": "alias" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "stack_stats": { + "properties": { + "apm": { + "properties": { + "found": { + "path": "elasticsearch.cluster.stats.stack.apm.found", + "type": "alias" + } + } + }, + "xpack": { + "properties": { + "ccr": { + "properties": { + "available": { + "path": "elasticsearch.cluster.stats.stack.xpack.ccr.available", + "type": "alias" + }, + "enabled": { + "path": "elasticsearch.cluster.stats.stack.xpack.ccr.enabled", + "type": "alias" + } + } + } + } + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.cluster.stats.version", + "elasticsearch.cluster.stats.state.nodes_hash", + "elasticsearch.cluster.stats.state.master_node", + "elasticsearch.cluster.stats.state.version", + "elasticsearch.cluster.stats.state.state_uuid", + "elasticsearch.cluster.stats.status", + "elasticsearch.cluster.stats.nodes.versions", + "elasticsearch.cluster.stats.license.issued_to", + "elasticsearch.cluster.stats.license.issuer", + "elasticsearch.cluster.stats.license.status", + "elasticsearch.cluster.stats.license.type", + "elasticsearch.cluster.stats.license.uid", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.version", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.enrich-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.enrich-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.enrich", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "enrich": { + "properties": { + "executed_searches": { + "properties": { + "total": { + "type": "long" + } + } + }, + "executing_policy": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "task": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "cancellable": { + "type": "boolean" + }, + "id": { + "type": "long" + }, + "parent_task_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "task": { + "ignore_above": 1024, + "type": "keyword" + }, + "time": { + "properties": { + "running": { + "properties": { + "nano": { + "type": "long" + } + } + }, + "start": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "queue": { + "properties": { + "size": { + "type": "long" + } + } + }, + "remote_requests": { + "properties": { + "current": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.enrich.executing_policy.name", + "elasticsearch.enrich.executing_policy.task.task", + "elasticsearch.enrich.executing_policy.task.action", + "elasticsearch.enrich.executing_policy.task.parent_task_id", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.index-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.index-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.index", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "index": { + "properties": { + "created": { + "type": "long" + }, + "hidden": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "primaries": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + }, + "throttle_time_in_millis": { + "type": "long" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "type": "long" + } + } + }, + "query_cache": { + "properties": { + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "refresh": { + "properties": { + "external_total_time_in_millis": { + "type": "long" + }, + "total_time_in_millis": { + "type": "long" + } + } + }, + "request_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "doc_values_memory_in_bytes": { + "type": "long" + }, + "fixed_bit_set_memory_in_bytes": { + "type": "long" + }, + "index_writer_memory_in_bytes": { + "type": "long" + }, + "memory_in_bytes": { + "type": "long" + }, + "norms_memory_in_bytes": { + "type": "long" + }, + "points_memory_in_bytes": { + "type": "long" + }, + "stored_fields_memory_in_bytes": { + "type": "long" + }, + "term_vectors_memory_in_bytes": { + "type": "long" + }, + "terms_memory_in_bytes": { + "type": "long" + }, + "version_map_memory_in_bytes": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + }, + "shards": { + "properties": { + "primaries": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "properties": { + "bulk": { + "properties": { + "avg_size_in_bytes": { + "type": "long" + }, + "avg_time_in_millis": { + "type": "long" + }, + "total_operations": { + "type": "long" + }, + "total_size_in_bytes": { + "type": "long" + }, + "total_time_in_millis": { + "type": "long" + } + } + }, + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "fielddata": { + "properties": { + "evictions": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "type": "long" + }, + "index_total": { + "type": "long" + }, + "throttle_time_in_millis": { + "type": "long" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "type": "long" + } + } + }, + "query_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "refresh": { + "properties": { + "external_total_time_in_millis": { + "type": "long" + }, + "total_time_in_millis": { + "type": "long" + } + } + }, + "request_cache": { + "properties": { + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "memory_size_in_bytes": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "type": "long" + }, + "query_total": { + "type": "long" + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "doc_values_memory_in_bytes": { + "type": "long" + }, + "fixed_bit_set_memory_in_bytes": { + "type": "long" + }, + "index_writer_memory_in_bytes": { + "type": "long" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "memory_in_bytes": { + "type": "long" + }, + "norms_memory_in_bytes": { + "type": "long" + }, + "points_memory_in_bytes": { + "type": "long" + }, + "stored_fields_memory_in_bytes": { + "type": "long" + }, + "term_vectors_memory_in_bytes": { + "type": "long" + }, + "terms_memory_in_bytes": { + "type": "long" + }, + "version_map_memory_in_bytes": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "size_in_bytes": { + "type": "long" + } + } + } + } + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "index_stats": { + "properties": { + "index": { + "path": "elasticsearch.index.name", + "type": "alias" + }, + "primaries": { + "properties": { + "docs": { + "properties": { + "count": { + "path": "elasticsearch.index.primaries.docs.count", + "type": "alias" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "path": "elasticsearch.index.primaries.indexing.index_time_in_millis", + "type": "alias" + }, + "index_total": { + "path": "elasticsearch.index.primaries.indexing.index_total", + "type": "alias" + }, + "throttle_time_in_millis": { + "path": "elasticsearch.index.primaries.indexing.throttle_time_in_millis", + "type": "alias" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "path": "elasticsearch.index.primaries.merges.total_size_in_bytes", + "type": "alias" + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { + "path": "elasticsearch.index.primaries.refresh.total_time_in_millis", + "type": "alias" + } + } + }, + "segments": { + "properties": { + "count": { + "path": "elasticsearch.index.primaries.segments.count", + "type": "alias" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "path": "elasticsearch.index.primaries.store.size_in_bytes", + "type": "alias" + } + } + } + } + }, + "total": { + "properties": { + "fielddata": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.index.total.fielddata.memory_size_in_bytes", + "type": "alias" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "path": "elasticsearch.index.total.indexing.index_time_in_millis", + "type": "alias" + }, + "index_total": { + "path": "elasticsearch.index.total.indexing.index_total", + "type": "alias" + }, + "throttle_time_in_millis": { + "path": "elasticsearch.index.total.indexing.throttle_time_in_millis", + "type": "alias" + } + } + }, + "merges": { + "properties": { + "total_size_in_bytes": { + "path": "elasticsearch.index.total.merges.total_size_in_bytes", + "type": "alias" + } + } + }, + "query_cache": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.index.total.query_cache.memory_size_in_bytes", + "type": "alias" + } + } + }, + "refresh": { + "properties": { + "total_time_in_millis": { + "path": "elasticsearch.index.total.refresh.total_time_in_millis", + "type": "alias" + } + } + }, + "request_cache": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.index.total.request_cache.memory_size_in_bytes", + "type": "alias" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "path": "elasticsearch.index.total.search.query_time_in_millis", + "type": "alias" + }, + "query_total": { + "path": "elasticsearch.index.total.search.query_total", + "type": "alias" + } + } + }, + "segments": { + "properties": { + "count": { + "path": "elasticsearch.index.total.segments.count", + "type": "alias" + }, + "doc_values_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.doc_values_memory_in_bytes", + "type": "alias" + }, + "fixed_bit_set_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.fixed_bit_set_memory_in_bytes", + "type": "alias" + }, + "index_writer_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.index_writer_memory_in_bytes", + "type": "alias" + }, + "memory_in_bytes": { + "path": "elasticsearch.index.total.segments.memory_in_bytes", + "type": "alias" + }, + "norms_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.norms_memory_in_bytes", + "type": "alias" + }, + "points_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.points_memory_in_bytes", + "type": "alias" + }, + "stored_fields_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.stored_fields_memory_in_bytes", + "type": "alias" + }, + "term_vectors_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.term_vectors_memory_in_bytes", + "type": "alias" + }, + "terms_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.terms_memory_in_bytes", + "type": "alias" + }, + "version_map_memory_in_bytes": { + "path": "elasticsearch.index.total.segments.version_map_memory_in_bytes", + "type": "alias" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "path": "elasticsearch.index.total.store.size_in_bytes", + "type": "alias" + } + } + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.index.uuid", + "elasticsearch.index.status", + "elasticsearch.index.name", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.index_recovery-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.index_recovery-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.index_recovery", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "index": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "recovery": { + "properties": { + "id": { + "type": "long" + }, + "index": { + "properties": { + "files": { + "properties": { + "percent": { + "ignore_above": 1024, + "type": "keyword" + }, + "recovered": { + "type": "long" + }, + "reused": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "size": { + "properties": { + "recovered_in_bytes": { + "type": "long" + }, + "reused_in_bytes": { + "type": "long" + }, + "total_in_bytes": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "primary": { + "type": "boolean" + }, + "source": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport_address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stage": { + "ignore_above": 1024, + "type": "keyword" + }, + "start_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "stop_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "target": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport_address": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "total_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "translog": { + "properties": { + "percent": { + "ignore_above": 1024, + "type": "keyword" + }, + "total": { + "type": "long" + }, + "total_on_start": { + "type": "long" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "verify_index": { + "properties": { + "check_index_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "total_time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "index_recovery": { + "properties": { + "shards": { + "properties": { + "start_time_in_millis": { + "path": "elasticsearch.index.recovery.start_time.ms", + "type": "alias" + }, + "stop_time_in_millis": { + "path": "elasticsearch.index.recovery.stop_time.ms", + "type": "alias" + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.index.name", + "elasticsearch.index.recovery.index.files.percent", + "elasticsearch.index.recovery.name", + "elasticsearch.index.recovery.type", + "elasticsearch.index.recovery.stage", + "elasticsearch.index.recovery.translog.percent", + "elasticsearch.index.recovery.target.transport_address", + "elasticsearch.index.recovery.target.id", + "elasticsearch.index.recovery.target.host", + "elasticsearch.index.recovery.target.name", + "elasticsearch.index.recovery.source.transport_address", + "elasticsearch.index.recovery.source.id", + "elasticsearch.index.recovery.source.host", + "elasticsearch.index.recovery.source.name", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.index_summary-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.index_summary-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.index_summary", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "index": { + "properties": { + "summary": { + "properties": { + "primaries": { + "properties": { + "bulk": { + "properties": { + "operations": { + "properties": { + "count": { + "type": "long" + } + } + }, + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "time": { + "properties": { + "avg": { + "properties": { + "bytes": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + }, + "count": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "search": { + "properties": { + "query": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "total": { + "properties": { + "bulk": { + "properties": { + "operations": { + "properties": { + "count": { + "type": "long" + } + } + }, + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "time": { + "properties": { + "avg": { + "properties": { + "bytes": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "indexing": { + "properties": { + "index": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "is_throttled": { + "type": "boolean" + }, + "throttle_time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "search": { + "properties": { + "query": { + "properties": { + "count": { + "type": "long" + }, + "time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "indices_stats": { + "properties": { + "_all": { + "properties": { + "primaries": { + "properties": { + "indexing": { + "properties": { + "index_time_in_millis": { + "path": "elasticsearch.index.summary.primaries.indexing.index.time.ms", + "type": "alias" + }, + "index_total": { + "path": "elasticsearch.index.summary.primaries.indexing.index.count", + "type": "alias" + } + } + } + } + }, + "total": { + "properties": { + "indexing": { + "properties": { + "index_total": { + "path": "elasticsearch.index.summary.total.indexing.index.count", + "type": "alias" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "path": "elasticsearch.index.summary.total.search.query.time.ms", + "type": "alias" + }, + "query_total": { + "path": "elasticsearch.index.summary.total.search.query.count", + "type": "alias" + } + } + } + } + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.node-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.node-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.node", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "jvm": { + "properties": { + "memory": { + "properties": { + "heap": { + "properties": { + "init": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "nonheap": { + "properties": { + "init": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "process": { + "properties": { + "mlockall": { + "type": "boolean" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.node.version", + "elasticsearch.node.jvm.version", + "elasticsearch.node.id", + "elasticsearch.node.name", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.node_stats-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.node_stats-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.node_stats", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "stats": { + "properties": { + "fs": { + "properties": { + "io_stats": { + "properties": { + "total": { + "properties": { + "operations": { + "properties": { + "count": { + "type": "long" + } + } + }, + "read": { + "properties": { + "operations": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "write": { + "properties": { + "operations": { + "properties": { + "count": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "summary": { + "properties": { + "available": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "free": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "total": { + "properties": { + "available_in_bytes": { + "type": "long" + }, + "total_in_bytes": { + "type": "long" + } + } + } + } + }, + "indexing_pressure": { + "properties": { + "memory": { + "properties": { + "current": { + "properties": { + "all": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "combined_coordinating_and_primary": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "coordinating": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "primary": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "replica": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "limit_in_bytes": { + "type": "long" + }, + "total": { + "properties": { + "all": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "combined_coordinating_and_primary": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "coordinating": { + "properties": { + "bytes": { + "type": "long" + }, + "rejections": { + "type": "long" + } + } + }, + "primary": { + "properties": { + "bytes": { + "type": "long" + }, + "rejections": { + "type": "long" + } + } + }, + "replica": { + "properties": { + "bytes": { + "type": "long" + }, + "rejections": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "indices": { + "properties": { + "bulk": { + "properties": { + "avg_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "avg_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "operations": { + "properties": { + "total": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "total_size": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total_time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "docs": { + "properties": { + "count": { + "type": "long" + }, + "deleted": { + "type": "long" + } + } + }, + "fielddata": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "indexing": { + "properties": { + "index_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "index_total": { + "properties": { + "count": { + "type": "long" + } + } + }, + "throttle_time": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "query_cache": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "request_cache": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "search": { + "properties": { + "query_time": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "query_total": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "segments": { + "properties": { + "count": { + "type": "long" + }, + "doc_values": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "fixed_bit_set": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "index_writer": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "norms": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "points": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "stored_fields": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "term_vectors": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "terms": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "version_map": { + "properties": { + "memory": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + }, + "ingest": { + "properties": { + "total": { + "properties": { + "count": { + "type": "long" + }, + "current": { + "type": "long" + }, + "failed": { + "type": "long" + }, + "time_in_millis": { + "type": "long" + } + } + } + } + }, + "jvm": { + "properties": { + "gc": { + "properties": { + "collectors": { + "properties": { + "old": { + "properties": { + "collection": { + "properties": { + "count": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + } + } + }, + "young": { + "properties": { + "collection": { + "properties": { + "count": { + "type": "long" + }, + "ms": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "mem": { + "properties": { + "heap": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + }, + "pct": { + "type": "double" + } + } + } + } + }, + "pools": { + "properties": { + "old": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak_max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "survivor": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak_max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "young": { + "properties": { + "max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "peak_max": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + } + } + } + } + } + } + }, + "os": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs": { + "properties": { + "quota": { + "properties": { + "us": { + "type": "long" + } + } + } + } + }, + "stat": { + "properties": { + "elapsed_periods": { + "properties": { + "count": { + "type": "long" + } + } + }, + "time_throttled": { + "properties": { + "ns": { + "type": "long" + } + } + }, + "times_throttled": { + "properties": { + "count": { + "type": "long" + } + } + } + } + } + } + }, + "cpuacct": { + "properties": { + "usage": { + "properties": { + "ns": { + "type": "long" + } + } + } + } + }, + "memory": { + "properties": { + "control_group": { + "ignore_above": 1024, + "type": "keyword" + }, + "limit": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "usage": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "cpu": { + "properties": { + "load_avg": { + "properties": { + "1m": { + "type": "half_float" + } + } + } + } + } + } + }, + "process": { + "properties": { + "cpu": { + "properties": { + "pct": { + "type": "double" + } + } + } + } + }, + "thread_pool": { + "properties": { + "bulk": { + "properties": { + "queue": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "get": { + "properties": { + "queue": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "index": { + "properties": { + "queue": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "search": { + "properties": { + "queue": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + }, + "write": { + "properties": { + "queue": { + "properties": { + "count": { + "type": "long" + } + } + }, + "rejected": { + "properties": { + "count": { + "type": "long" + } + } + } + } + } + } + } + } + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node_stats": { + "properties": { + "fs": { + "properties": { + "io_stats": { + "properties": { + "total": { + "properties": { + "operations": { + "path": "elasticsearch.node.stats.fs.io_stats.total.operations.count", + "type": "alias" + }, + "read_operations": { + "path": "elasticsearch.node.stats.fs.io_stats.total.read.operations.count", + "type": "alias" + }, + "write_operations": { + "path": "elasticsearch.node.stats.fs.io_stats.total.write.operations.count", + "type": "alias" + } + } + } + } + }, + "summary": { + "properties": { + "available": { + "properties": { + "bytes": { + "path": "elasticsearch.node.stats.fs.summary.available.bytes", + "type": "alias" + } + } + }, + "total": { + "properties": { + "bytes": { + "path": "elasticsearch.node.stats.fs.summary.total.bytes", + "type": "alias" + } + } + } + } + }, + "total": { + "properties": { + "available_in_bytes": { + "path": "elasticsearch.node.stats.fs.summary.available.bytes", + "type": "alias" + }, + "total_in_bytes": { + "path": "elasticsearch.node.stats.fs.summary.total.bytes", + "type": "alias" + } + } + } + } + }, + "indices": { + "properties": { + "docs": { + "properties": { + "count": { + "path": "elasticsearch.node.stats.indices.docs.count", + "type": "alias" + } + } + }, + "fielddata": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.node.stats.indices.fielddata.memory.bytes", + "type": "alias" + } + } + }, + "indexing": { + "properties": { + "index_time_in_millis": { + "path": "elasticsearch.node.stats.indices.indexing.index_time.ms", + "type": "alias" + }, + "index_total": { + "path": "elasticsearch.node.stats.indices.indexing.index_total.count", + "type": "alias" + }, + "throttle_time_in_millis": { + "path": "elasticsearch.node.stats.indices.indexing.throttle_time.ms", + "type": "alias" + } + } + }, + "query_cache": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.node.stats.indices.query_cache.memory.bytes", + "type": "alias" + } + } + }, + "request_cache": { + "properties": { + "memory_size_in_bytes": { + "path": "elasticsearch.node.stats.indices.request_cache.memory.bytes", + "type": "alias" + } + } + }, + "search": { + "properties": { + "query_time_in_millis": { + "path": "elasticsearch.node.stats.indices.search.query_time.ms", + "type": "alias" + }, + "query_total": { + "path": "elasticsearch.node.stats.indices.search.query_total.count", + "type": "alias" + } + } + }, + "segments": { + "properties": { + "count": { + "path": "elasticsearch.node.stats.indices.segments.count", + "type": "alias" + }, + "doc_values_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.doc_values.memory.bytes", + "type": "alias" + }, + "fixed_bit_set_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.fixed_bit_set.memory.bytes", + "type": "alias" + }, + "index_writer_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.index_writer.memory.bytes", + "type": "alias" + }, + "memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.memory.bytes", + "type": "alias" + }, + "norms_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.norms.memory.bytes", + "type": "alias" + }, + "points_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.points.memory.bytes", + "type": "alias" + }, + "stored_fields_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.stored_fields.memory.bytes", + "type": "alias" + }, + "term_vectors_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.term_vectors.memory.bytes", + "type": "alias" + }, + "terms_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.terms.memory.bytes", + "type": "alias" + }, + "version_map_memory_in_bytes": { + "path": "elasticsearch.node.stats.indices.segments.version_map.memory.bytes", + "type": "alias" + } + } + }, + "store": { + "properties": { + "size": { + "properties": { + "bytes": { + "path": "elasticsearch.node.stats.indices.store.size.bytes", + "type": "alias" + } + } + }, + "size_in_bytes": { + "path": "elasticsearch.node.stats.indices.store.size.bytes", + "type": "alias" + } + } + } + } + }, + "jvm": { + "properties": { + "gc": { + "properties": { + "collectors": { + "properties": { + "old": { + "properties": { + "collection_count": { + "path": "elasticsearch.node.stats.jvm.gc.collectors.old.collection.count", + "type": "alias" + }, + "collection_time_in_millis": { + "path": "elasticsearch.node.stats.jvm.gc.collectors.old.collection.ms", + "type": "alias" + } + } + }, + "young": { + "properties": { + "collection_count": { + "path": "elasticsearch.node.stats.jvm.gc.collectors.young.collection.count", + "type": "alias" + }, + "collection_time_in_millis": { + "path": "elasticsearch.node.stats.jvm.gc.collectors.young.collection.ms", + "type": "alias" + } + } + } + } + } + } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "path": "elasticsearch.node.stats.jvm.mem.heap.max.bytes", + "type": "alias" + }, + "heap_used_in_bytes": { + "path": "elasticsearch.node.stats.jvm.mem.heap.used.bytes", + "type": "alias" + }, + "heap_used_percent": { + "path": "elasticsearch.node.stats.jvm.mem.heap.used.pct", + "type": "alias" + } + } + } + } + }, + "node_id": { + "path": "elasticsearch.node.id", + "type": "alias" + }, + "os": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs_quota_micros": { + "path": "elasticsearch.node.stats.os.cgroup.cpu.cfs.quota.us", + "type": "alias" + }, + "stat": { + "properties": { + "number_of_elapsed_periods": { + "path": "elasticsearch.node.stats.os.cgroup.cpu.stat.elapsed_periods.count", + "type": "alias" + }, + "number_of_times_throttled": { + "path": "elasticsearch.node.stats.os.cgroup.cpu.stat.times_throttled.count", + "type": "alias" + }, + "time_throttled_nanos": { + "path": "elasticsearch.node.stats.os.cgroup.cpu.stat.time_throttled.ns", + "type": "alias" + } + } + } + } + }, + "cpuacct": { + "properties": { + "usage_nanos": { + "path": "elasticsearch.node.stats.os.cgroup.cpuacct.usage.ns", + "type": "alias" + } + } + }, + "memory": { + "properties": { + "control_group": { + "path": "elasticsearch.node.stats.os.cgroup.memory.control_group", + "type": "alias" + }, + "limit_in_bytes": { + "path": "elasticsearch.node.stats.os.cgroup.memory.limit.bytes", + "type": "alias" + }, + "usage_in_bytes": { + "path": "elasticsearch.node.stats.os.cgroup.memory.usage.bytes", + "type": "alias" + } + } + } + } + }, + "cpu": { + "properties": { + "load_average": { + "properties": { + "1m": { + "path": "elasticsearch.node.stats.os.cpu.load_avg.1m", + "type": "alias" + } + } + } + } + } + } + }, + "process": { + "properties": { + "cpu": { + "properties": { + "percent": { + "path": "elasticsearch.node.stats.process.cpu.pct", + "type": "alias" + } + } + } + } + }, + "thread_pool": { + "properties": { + "bulk": { + "properties": { + "queue": { + "path": "elasticsearch.node.stats.thread_pool.bulk.queue.count", + "type": "alias" + }, + "rejected": { + "path": "elasticsearch.node.stats.thread_pool.bulk.rejected.count", + "type": "alias" + } + } + }, + "get": { + "properties": { + "queue": { + "path": "elasticsearch.node.stats.thread_pool.get.queue.count", + "type": "alias" + }, + "rejected": { + "path": "elasticsearch.node.stats.thread_pool.get.rejected.count", + "type": "alias" + } + } + }, + "index": { + "properties": { + "queue": { + "path": "elasticsearch.node.stats.thread_pool.index.queue.count", + "type": "alias" + }, + "rejected": { + "path": "elasticsearch.node.stats.thread_pool.index.rejected.count", + "type": "alias" + } + } + }, + "search": { + "properties": { + "queue": { + "path": "elasticsearch.node.stats.thread_pool.search.queue.count", + "type": "alias" + }, + "rejected": { + "path": "elasticsearch.node.stats.thread_pool.search.rejected.count", + "type": "alias" + } + } + }, + "write": { + "properties": { + "queue": { + "path": "elasticsearch.node.stats.thread_pool.write.queue.count", + "type": "alias" + }, + "rejected": { + "path": "elasticsearch.node.stats.thread_pool.write.rejected.count", + "type": "alias" + } + } + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.node.stats.os.cgroup.memory.control_group", + "elasticsearch.node.stats.os.cgroup.memory.limit.bytes", + "elasticsearch.node.stats.os.cgroup.memory.usage.bytes", + "elasticsearch.node.id", + "elasticsearch.node.name", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-elasticsearch.stack_monitoring.shard-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-elasticsearch.stack_monitoring.shard-*" + ], + "name": "metrics-elasticsearch.stack_monitoring.shard", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "elasticsearch" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stats": { + "properties": { + "state": { + "properties": { + "state_uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "index": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "master": { + "type": "boolean" + }, + "mlockall": { + "type": "boolean" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "shard": { + "properties": { + "number": { + "type": "long" + }, + "primary": { + "type": "boolean" + }, + "relocating_node": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source_node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "shard": { + "properties": { + "index": { + "path": "elasticsearch.index.name", + "type": "alias" + }, + "node": { + "path": "elasticsearch.node.id", + "type": "alias" + }, + "primary": { + "path": "elasticsearch.shard.primary", + "type": "alias" + }, + "shard": { + "path": "elasticsearch.shard.number", + "type": "alias" + }, + "state": { + "path": "elasticsearch.shard.state", + "type": "alias" + } + } + }, + "source_node": { + "properties": { + "name": { + "path": "elasticsearch.node.name", + "type": "alias" + }, + "uuid": { + "path": "elasticsearch.node.id", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "service.address", + "service.type", + "service.name", + "error.message", + "elasticsearch.shard.state", + "elasticsearch.shard.relocating_node.name", + "elasticsearch.shard.relocating_node.id", + "elasticsearch.shard.source_node.name", + "elasticsearch.shard.source_node.uuid", + "elasticsearch.index.name", + "elasticsearch.cluster.name", + "elasticsearch.cluster.id", + "elasticsearch.cluster.state.id", + "elasticsearch.cluster.stats.state.state_uuid", + "elasticsearch.node.id", + "elasticsearch.node.name" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.cluster_actions-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.cluster_actions-*" + ], + "name": "metrics-kibana.stack_monitoring.cluster_actions", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "kibana.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "cluster_actions": { + "properties": { + "overdue": { + "properties": { + "count": { + "type": "long" + }, + "delay": { + "properties": { + "p50": { + "type": "float" + }, + "p99": { + "type": "float" + } + } + } + } + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "kibana_stats": { + "properties": { + "kibana": { + "properties": { + "uuid": { + "path": "service.id", + "type": "alias" + }, + "version": { + "path": "service.version", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.address", + "service.version", + "service.type", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "kibana.elasticsearch.cluster.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.cluster_rules-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.cluster_rules-*" + ], + "name": "metrics-kibana.stack_monitoring.cluster_rules", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "kibana.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "cluster_rules": { + "properties": { + "overdue": { + "properties": { + "count": { + "type": "long" + }, + "delay": { + "properties": { + "p50": { + "type": "float" + }, + "p99": { + "type": "float" + } + } + } + } + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "kibana_stats": { + "properties": { + "kibana": { + "properties": { + "uuid": { + "path": "service.id", + "type": "alias" + }, + "version": { + "path": "service.version", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.address", + "service.version", + "service.type", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "kibana.elasticsearch.cluster.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.node_actions-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.node_actions-*" + ], + "name": "metrics-kibana.stack_monitoring.node_actions", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "kibana.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node_actions": { + "properties": { + "executions": { + "type": "long" + }, + "failures": { + "type": "long" + }, + "timeouts": { + "type": "long" + } + } + } + } + }, + "kibana_stats": { + "properties": { + "kibana": { + "properties": { + "uuid": { + "path": "service.id", + "type": "alias" + }, + "version": { + "path": "service.version", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.address", + "service.version", + "service.type", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "kibana.elasticsearch.cluster.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.node_rules-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.node_rules-*" + ], + "name": "metrics-kibana.stack_monitoring.node_rules", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "kibana.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node_rules": { + "properties": { + "executions": { + "type": "long" + }, + "failures": { + "type": "long" + }, + "timeouts": { + "type": "long" + } + } + } + } + }, + "kibana_stats": { + "properties": { + "kibana": { + "properties": { + "uuid": { + "path": "service.id", + "type": "alias" + }, + "version": { + "path": "service.version", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.address", + "service.version", + "service.type", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "kibana.elasticsearch.cluster.id" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.stats-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.stats-*" + ], + "name": "metrics-kibana.stack_monitoring.stats", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "kibana.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "stats": { + "properties": { + "concurrent_connections": { + "type": "long" + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "index": { + "ignore_above": 1024, + "type": "keyword" + }, + "kibana": { + "properties": { + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "distro": { + "ignore_above": 1024, + "type": "keyword" + }, + "distroRelease": { + "ignore_above": 1024, + "type": "keyword" + }, + "load": { + "properties": { + "15m": { + "type": "half_float" + }, + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + } + } + }, + "memory": { + "properties": { + "free_in_bytes": { + "type": "long" + }, + "total_in_bytes": { + "type": "long" + }, + "used_in_bytes": { + "type": "long" + } + } + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "platformRelease": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "event_loop_delay": { + "properties": { + "ms": { + "scaling_factor": 1000, + "type": "scaled_float" + } + } + }, + "memory": { + "properties": { + "heap": { + "properties": { + "size_limit": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "total": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "uptime": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "used": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "resident_set_size": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "uptime": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "request": { + "properties": { + "disconnects": { + "type": "long" + }, + "total": { + "type": "long" + } + } + }, + "response_time": { + "properties": { + "avg": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "max": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "snapshot": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "usage": { + "properties": { + "index": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "kibana_stats": { + "properties": { + "concurrent_connections": { + "path": "kibana.stats.concurrent_connections", + "type": "alias" + }, + "kibana": { + "properties": { + "response_time": { + "properties": { + "max": { + "path": "kibana.stats.response_time.max.ms", + "type": "alias" + } + } + }, + "status": { + "path": "kibana.stats.status", + "type": "alias" + }, + "uuid": { + "path": "service.id", + "type": "alias" + }, + "version": { + "path": "service.version", + "type": "alias" + } + } + }, + "os": { + "properties": { + "load": { + "properties": { + "15m": { + "path": "kibana.stats.os.load.15m", + "type": "alias" + }, + "1m": { + "path": "kibana.stats.os.load.1m", + "type": "alias" + }, + "5m": { + "path": "kibana.stats.os.load.5m", + "type": "alias" + } + } + }, + "memory": { + "properties": { + "free_in_bytes": { + "path": "kibana.stats.os.memory.free_in_bytes", + "type": "alias" + } + } + } + } + }, + "process": { + "properties": { + "event_loop_delay": { + "path": "kibana.stats.process.event_loop_delay.ms", + "type": "alias" + }, + "memory": { + "properties": { + "heap": { + "properties": { + "size_limit": { + "path": "kibana.stats.process.memory.heap.size_limit.bytes", + "type": "alias" + } + } + }, + "resident_set_size_in_bytes": { + "path": "kibana.stats.process.memory.resident_set_size.bytes", + "type": "alias" + } + } + }, + "uptime_in_millis": { + "path": "kibana.stats.process.uptime.ms", + "type": "alias" + } + } + }, + "requests": { + "properties": { + "disconnects": { + "path": "kibana.stats.request.disconnects", + "type": "alias" + }, + "total": { + "path": "kibana.stats.request.total", + "type": "alias" + } + } + }, + "response_times": { + "properties": { + "average": { + "path": "kibana.stats.response_time.avg.ms", + "type": "alias" + }, + "max": { + "path": "kibana.stats.response_time.max.ms", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.address", + "service.version", + "service.type", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "kibana.elasticsearch.cluster.id", + "kibana.stats.kibana.status", + "kibana.stats.usage.index", + "kibana.stats.name", + "kibana.stats.index", + "kibana.stats.host.name", + "kibana.stats.status", + "kibana.stats.transport_address", + "kibana.stats.os.distro", + "kibana.stats.os.distroRelease", + "kibana.stats.os.platform", + "kibana.stats.os.platformRelease" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-kibana.stack_monitoring.status-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-kibana.stack_monitoring.status-*" + ], + "name": "metrics-kibana.stack_monitoring.status", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "kibana" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + } + } + }, + "kibana": { + "properties": { + "status": { + "properties": { + "metrics": { + "properties": { + "concurrent_connections": { + "type": "long" + }, + "requests": { + "properties": { + "disconnects": { + "type": "long" + }, + "total": { + "type": "long" + } + } + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "properties": { + "overall": { + "properties": { + "state": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.id", + "service.name", + "service.version", + "service.type", + "service.address", + "ecs.version", + "error.message", + "kibana.status.name", + "kibana.status.status.overall.state" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-logstash.stack_monitoring.node-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "logstash" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-logstash.stack_monitoring.node-*" + ], + "name": "metrics-logstash.stack_monitoring.node", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "logstash" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "logstash.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "logstash": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "jvm": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "properties": { + "pipeline": { + "properties": { + "batch_size": { + "type": "long" + }, + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "representation": { + "properties": { + "graph": { + "properties": { + "edges": { + "type": "object" + }, + "vertices": { + "type": "object" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "workers": { + "type": "long" + } + } + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "logstash_state": { + "properties": { + "pipeline": { + "properties": { + "hash": { + "path": "logstash.node.state.pipeline.hash", + "type": "alias" + }, + "id": { + "path": "logstash.node.state.pipeline.id", + "type": "alias" + } + } + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.hostname", + "service.id", + "service.type", + "service.version", + "service.address", + "service.name", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "logstash.cluster.id", + "logstash.elasticsearch.cluster.id", + "logstash.node.jvm.version", + "logstash.node.host", + "logstash.node.version", + "logstash.node.id", + "logstash.node.state.pipeline.id", + "logstash.node.state.pipeline.hash", + "logstash.node.state.pipeline.ephemeral_id", + "logstash.node.state.pipeline.representation.hash", + "logstash.node.state.pipeline.representation.type", + "logstash.node.state.pipeline.representation.version" + ] + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "metrics-logstash.stack_monitoring.node_stats-default", + "template": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "logstash" + } + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "metrics-logstash.stack_monitoring.node_stats-*" + ], + "name": "metrics-logstash.stack_monitoring.node_stats", + "priority": 200, + "template": { + "mappings": { + "_meta": { + "managed": true, + "managed_by": "fleet", + "package": { + "name": "logstash" + } + }, + "date_detection": false, + "dynamic": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "cluster_uuid": { + "path": "logstash.elasticsearch.cluster.id", + "type": "alias" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "message": { + "type": "match_only_text" + } + } + }, + "event": { + "properties": { + "agent_id_status": { + "ignore_above": 1024, + "type": "keyword" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "ingested": { + "format": "strict_date_time_no_millis||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "logstash": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elasticsearch": { + "properties": { + "cluster": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "node": { + "properties": { + "state": { + "properties": { + "pipeline": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "stats": { + "properties": { + "events": { + "properties": { + "duration_in_millis": { + "type": "long" + }, + "filtered": { + "type": "long" + }, + "in": { + "type": "long" + }, + "out": { + "type": "long" + } + } + }, + "jvm": { + "properties": { + "gc": { + "properties": { + "collectors": { + "properties": { + "old": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + }, + "young": { + "properties": { + "collection_count": { + "type": "long" + }, + "collection_time_in_millis": { + "type": "long" + } + } + } + } + } + } + }, + "mem": { + "properties": { + "heap_max_in_bytes": { + "type": "long" + }, + "heap_used_in_bytes": { + "type": "long" + }, + "heap_used_percent": { + "type": "long" + } + } + }, + "uptime_in_millis": { + "type": "long" + } + } + }, + "logstash": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "host": { + "ignore_above": 1024, + "type": "keyword" + }, + "http_address": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pipeline": { + "properties": { + "batch_size": { + "type": "long" + }, + "workers": { + "type": "long" + } + } + }, + "snapshot": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "cgroup": { + "properties": { + "cpu": { + "properties": { + "cfs_quota_micros": { + "type": "long" + }, + "control_group": { + "type": "text" + }, + "stat": { + "type": "object" + } + } + }, + "cpuacct": { + "type": "object" + } + } + }, + "cpu": { + "properties": { + "load_average": { + "properties": { + "15m": { + "type": "half_float" + }, + "1m": { + "type": "half_float" + }, + "5m": { + "type": "half_float" + } + } + }, + "percent": { + "type": "double" + } + } + } + } + }, + "pipelines": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "events": { + "properties": { + "duration_in_millis": { + "type": "long" + }, + "filtered": { + "type": "long" + }, + "in": { + "type": "long" + }, + "out": { + "type": "long" + }, + "queue_push_duration_in_millis": { + "type": "long" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "queue": { + "properties": { + "events_count": { + "type": "long" + }, + "max_queue_size_in_bytes": { + "type": "long" + }, + "queue_size_in_bytes": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "reloads": { + "properties": { + "failures": { + "type": "long" + }, + "successes": { + "type": "long" + } + } + }, + "vertices": { + "properties": { + "duration_in_millis": { + "type": "long" + }, + "events_in": { + "type": "long" + }, + "events_out": { + "type": "long" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "long_counters": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "type": "long" + } + }, + "type": "nested" + }, + "pipeline_ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "queue_push_duration_in_millis": { + "type": "long" + } + }, + "type": "nested" + } + }, + "type": "nested" + }, + "process": { + "properties": { + "cpu": { + "properties": { + "percent": { + "type": "double" + } + } + }, + "max_file_descriptors": { + "type": "long" + }, + "open_file_descriptors": { + "type": "long" + } + } + }, + "queue": { + "properties": { + "events_count": { + "type": "long" + } + } + }, + "reloads": { + "properties": { + "failures": { + "type": "long" + }, + "successes": { + "type": "long" + } + } + }, + "timestamp": { + "type": "date" + } + } + } + } + } + } + }, + "logstash_stats": { + "properties": { + "events": { + "properties": { + "duration_in_millis": { + "path": "logstash.node.stats.events.duration_in_millis", + "type": "alias" + }, + "in": { + "path": "logstash.node.stats.events.in", + "type": "alias" + }, + "out": { + "path": "logstash.node.stats.events.out", + "type": "alias" + } + } + }, + "jvm": { + "properties": { + "mem": { + "properties": { + "heap_max_in_bytes": { + "path": "logstash.node.stats.jvm.mem.heap_max_in_bytes", + "type": "alias" + }, + "heap_used_in_bytes": { + "path": "logstash.node.stats.jvm.mem.heap_used_in_bytes", + "type": "alias" + } + } + }, + "uptime_in_millis": { + "path": "logstash.node.stats.jvm.uptime_in_millis", + "type": "alias" + } + } + }, + "logstash": { + "properties": { + "uuid": { + "path": "logstash.node.stats.logstash.uuid", + "type": "alias" + }, + "version": { + "path": "logstash.node.stats.logstash.version", + "type": "alias" + } + } + }, + "os": { + "properties": { + "cgroup": { + "properties": { + "cpuacct": { + "type": "object" + } + } + }, + "cpu": { + "properties": { + "load_average": { + "properties": { + "15m": { + "path": "logstash.node.stats.os.cpu.load_average.15m", + "type": "alias" + }, + "1m": { + "path": "logstash.node.stats.os.cpu.load_average.1m", + "type": "alias" + }, + "5m": { + "path": "logstash.node.stats.os.cpu.load_average.5m", + "type": "alias" + } + } + }, + "stat": { + "type": "object" + } + } + } + } + }, + "pipelines": { + "type": "nested" + }, + "process": { + "properties": { + "cpu": { + "properties": { + "percent": { + "path": "logstash.node.stats.process.cpu.percent", + "type": "alias" + } + } + } + } + }, + "queue": { + "properties": { + "events_count": { + "path": "logstash.node.stats.queue.events_count", + "type": "alias" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "process": { + "properties": { + "pid": { + "type": "long" + } + } + }, + "service": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timestamp": { + "path": "@timestamp", + "type": "alias" + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "query": { + "default_field": [ + "service.hostname", + "service.id", + "service.type", + "service.version", + "service.address", + "service.name", + "ecs.version", + "event.dataset", + "event.module", + "host.name", + "error.message", + "logstash.elasticsearch.cluster.id", + "logstash.node.state.pipeline.id", + "logstash.node.state.pipeline.hash", + "logstash.node.stats.logstash.uuid", + "logstash.node.stats.logstash.version", + "logstash.node.stats.logstash.ephemeral_id", + "logstash.node.stats.logstash.host", + "logstash.node.stats.logstash.http_address", + "logstash.node.stats.logstash.name", + "logstash.node.stats.logstash.status", + "logstash.node.stats.os.cgroup.cpuacct.control_group", + "logstash.node.stats.os.cgroup.cpu.control_group", + "logstash.node.stats.pipelines.id", + "logstash.node.stats.pipelines.hash", + "logstash.node.stats.pipelines.ephemeral_id", + "logstash.node.stats.pipelines.queue.type", + "logstash.node.stats.pipelines.vertices.id", + "logstash.node.stats.pipelines.vertices.long_counters.name", + "logstash.node.stats.pipelines.vertices.pipeline_ephemeral_id", + "logstash.cluster.id" + ] + } + } + } + } + } + } +} diff --git a/x-pack/test/functional/es_archives/security_solution/suppression/data.json b/x-pack/test/functional/es_archives/security_solution/suppression/data.json new file mode 100644 index 00000000000000..6c22353d8227bb --- /dev/null +++ b/x-pack/test/functional/es_archives/security_solution/suppression/data.json @@ -0,0 +1,612 @@ +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:00.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1", + "destination.ip": ["127.0.0.1", "127.0.0.2"] + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:01.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:02.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:00.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:01.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:02.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:00.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:01.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:02.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:00.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:01.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:02.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:00.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:01.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:02.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:00.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:01.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T05:00:02.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:00.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:01.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:02.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:00.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:01.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:02.000Z", + "host": { + "name": "host-0", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:00.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:01.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:02.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:00.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:01.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:02.000Z", + "host": { + "name": "host-1", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:00.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:01.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:02.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.1.1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:00.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:01.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "index": "suppression-data", + "source": { + "@timestamp": "2020-10-28T06:00:02.000Z", + "host": { + "name": "host-2", + "ip": "127.0.0.1" + }, + "user.name": "user-0", + "source.ip": "192.168.2.2" + }, + "type": "_doc" + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/security_solution/suppression/mappings.json b/x-pack/test/functional/es_archives/security_solution/suppression/mappings.json new file mode 100644 index 00000000000000..3222d9bcc490aa --- /dev/null +++ b/x-pack/test/functional/es_archives/security_solution/suppression/mappings.json @@ -0,0 +1,50 @@ +{ + "type": "index", + "value": { + "index": "suppression-data", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "host": { + "properties": { + "name": { + "type": "keyword" + }, + "ip": { + "type": "ip" + } + } + }, + "user": { + "properties": { + "name": { + "type": "keyword" + } + } + }, + "source": { + "properties": { + "ip": { + "type": "ip" + } + } + }, + "destination": { + "properties": { + "ip": { + "type": "ip" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/services/ml/data_visualizer_table.ts b/x-pack/test/functional/services/ml/data_visualizer_table.ts index ace274f5cabf7e..847e34f305c6cd 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_table.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_table.ts @@ -290,25 +290,6 @@ export function MachineLearningDataVisualizerTableProvider( await testSubjects.existOrFail('dataVisualizerFieldTypeSelect'); } - public async assertSampleSizeInputExists() { - await testSubjects.existOrFail('dataVisualizerShardSizeSelect'); - } - - public async setSampleSizeInputValue( - sampleSize: number | 'all', - fieldName: string, - docCountFormatted: string - ) { - await this.assertSampleSizeInputExists(); - await testSubjects.clickWhenNotDisabledWithoutRetry('dataVisualizerShardSizeSelect'); - await testSubjects.existOrFail(`dataVisualizerShardSizeOption ${sampleSize}`); - await testSubjects.click(`dataVisualizerShardSizeOption ${sampleSize}`); - - await retry.tryForTime(5000, async () => { - await this.assertFieldDocCount(fieldName, docCountFormatted); - }); - } - public async setFieldTypeFilter(fieldTypes: string[], expectedRowCount = 1) { await this.assertFieldTypeInputExists(); await mlCommonUI.setMultiSelectFilter('dataVisualizerFieldTypeSelect', fieldTypes); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts index ee111bfc9d0c66..47006f7a1a7d18 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts @@ -368,19 +368,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await refreshAlertsList(); await find.waitForDeletedByCssSelector('.euiBasicTable-loading'); const refreshResults = await pageObjects.triggersActionsUI.getAlertsListWithStatus(); - expect(refreshResults.map((item: any) => item.status).sort()).to.eql(['Error', 'Ok']); + expect(refreshResults.map((item: any) => item.status).sort()).to.eql([ + 'Failed', + 'Succeeded', + ]); }); await refreshAlertsList(); await find.waitForDeletedByCssSelector('.euiBasicTable-loading'); - await testSubjects.click('ruleExecutionStatusFilterButton'); - await testSubjects.click('ruleExecutionStatuserrorFilterOption'); // select Error status filter + await testSubjects.click('ruleLastRunOutcomeFilterButton'); + await testSubjects.click('ruleLastRunOutcomefailedFilterOption'); // select Error status filter await retry.try(async () => { const filterErrorOnlyResults = await pageObjects.triggersActionsUI.getAlertsListWithStatus(); expect(filterErrorOnlyResults.length).to.equal(1); expect(filterErrorOnlyResults[0].name).to.equal(`${failingAlert.name}Test: Failing`); expect(filterErrorOnlyResults[0].interval).to.equal('30 sec'); - expect(filterErrorOnlyResults[0].status).to.equal('Error'); + expect(filterErrorOnlyResults[0].status).to.equal('Failed'); expect(filterErrorOnlyResults[0].duration).to.match(/\d{2,}:\d{2}/); }); }); @@ -393,7 +396,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(refreshResults.length).to.equal(1); expect(refreshResults[0].name).to.equal(`${createdAlert.name}Test: Noop`); expect(refreshResults[0].interval).to.equal('1 min'); - expect(refreshResults[0].status).to.equal('Ok'); + expect(refreshResults[0].status).to.equal('Succeeded'); expect(refreshResults[0].duration).to.match(/\d{2,}:\d{2}/); }); @@ -417,11 +420,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await retry.try(async () => { await refreshAlertsList(); expect(await testSubjects.getVisibleText('totalRulesCount')).to.be('2 rules'); - expect(await testSubjects.getVisibleText('totalActiveRulesCount')).to.be('Active: 0'); - expect(await testSubjects.getVisibleText('totalOkRulesCount')).to.be('Ok: 1'); - expect(await testSubjects.getVisibleText('totalErrorRulesCount')).to.be('Error: 1'); - expect(await testSubjects.getVisibleText('totalPendingRulesCount')).to.be('Pending: 0'); - expect(await testSubjects.getVisibleText('totalUnknownRulesCount')).to.be('Unknown: 0'); + expect(await testSubjects.getVisibleText('totalSucceededRulesCount')).to.be('Succeeded: 1'); + expect(await testSubjects.getVisibleText('totalFailedRulesCount')).to.be('Failed: 1'); + expect(await testSubjects.getVisibleText('totalWarningRulesCount')).to.be('Warning: 0'); }); }); @@ -433,7 +434,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(refreshResults.length).to.equal(1); expect(refreshResults[0].name).to.equal(`${createdAlert.name}Test: Noop`); expect(refreshResults[0].interval).to.equal('1 min'); - expect(refreshResults[0].status).to.equal('Ok'); + expect(refreshResults[0].status).to.equal('Succeeded'); expect(refreshResults[0].duration).to.match(/\d{2,}:\d{2}/); }); diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts index 6ca556876d0e7d..55cbf68ead3e04 100644 --- a/x-pack/test/functional_with_es_ssl/config.ts +++ b/x-pack/test/functional_with_es_ssl/config.ts @@ -93,6 +93,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { 'internalAlertsTable', 'ruleTagFilter', 'ruleStatusFilter', + 'ruleLastRunOutcome', ])}`, `--xpack.alerting.rules.minimumScheduleInterval.value="2s"`, `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts index 7052dcba7ff230..8c386e99423ce9 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/index.ts @@ -85,6 +85,13 @@ export default ({ getService }: FtrProviderContext) => { await testSubjects.existOrFail('autocompleteSuggestion-field-kibana.alert.status-'); }); + it('Invalid input should not break the page', async () => { + await observability.alerts.common.submitQuery('""""'); + await testSubjects.existOrFail('errorToastMessage'); + // Page should not go blank with invalid input + await testSubjects.existOrFail('alertsPageWithData'); + }); + it('Applies filters correctly', async () => { await observability.alerts.common.submitQuery('kibana.alert.status: recovered'); await retry.try(async () => { diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/geographic_data.ts b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/geographic_data.ts index c387599d505846..8c7bb633a2fa2a 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/geographic_data.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/geographic_data.ts @@ -103,11 +103,6 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await ml.testExecution.logTestStep('set data visualizer options'); await ml.dataVisualizerIndexBased.assertTimeRangeSelectorSectionExists(); await ml.dataVisualizerIndexBased.clickUseFullDataButton('14,074'); - await ml.dataVisualizerTable.setSampleSizeInputValue( - 'all', - 'geo.coordinates', - '14074 (100%)' - ); await ml.dataVisualizerTable.setFieldTypeFilter([ML_JOB_FIELD_TYPES.GEO_POINT]); await ml.testExecution.logTestStep('set maps options and take screenshot'); diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/mapping_anomalies.ts b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/mapping_anomalies.ts index 4684b8ffc176ca..c6affe8bf3c3a2 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/mapping_anomalies.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/mapping_anomalies.ts @@ -66,11 +66,6 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await ml.testExecution.logTestStep('set data visualizer options'); await ml.dataVisualizerIndexBased.assertTimeRangeSelectorSectionExists(); await ml.dataVisualizerIndexBased.clickUseFullDataButton('14,074'); - await ml.dataVisualizerTable.setSampleSizeInputValue( - 'all', - 'geo.coordinates', - '14074 (100%)' - ); await ml.dataVisualizerTable.setFieldNameFilter(['geo.dest']); await ml.testExecution.logTestStep('set maps options and take screenshot'); diff --git a/x-pack/test/security_solution_endpoint/config.ts b/x-pack/test/security_solution_endpoint/config.ts index 0673e28888d10d..00310d7a69938d 100644 --- a/x-pack/test/security_solution_endpoint/config.ts +++ b/x-pack/test/security_solution_endpoint/config.ts @@ -51,6 +51,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.fleet.packages.0.version=latest`, // set the packagerTaskInterval to 5s in order to speed up test executions when checking fleet artifacts '--xpack.securitySolution.packagerTaskInterval=5s', + // this will be removed in 8.7 when the file upload feature is released + `--xpack.fleet.enableExperimental.0=diagnosticFileUploadEnabled`, ], }, layout: { diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/file_upload_index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/file_upload_index.ts new file mode 100644 index 00000000000000..ce8179a050c47d --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/apis/file_upload_index.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + FILE_STORAGE_DATA_INDEX, + FILE_STORAGE_METADATA_INDEX, +} from '@kbn/security-solution-plugin/common/endpoint/constants'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const esClient = getService('es'); + + describe('File upload indices', () => { + it('should have created the file data index on install', async () => { + const endpointFileUploadIndexExists = await esClient.indices.exists({ + index: FILE_STORAGE_METADATA_INDEX, + }); + + expect(endpointFileUploadIndexExists).equal(true); + }); + it('should have created the files index on install', async () => { + const endpointFileUploadIndexExists = await esClient.indices.exists({ + index: FILE_STORAGE_DATA_INDEX, + }); + + expect(endpointFileUploadIndexExists).equal(true); + }); + }); +} diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts index 7be4ce22433032..22a7a5d7567efc 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts @@ -32,6 +32,7 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider loadTestFile(require.resolve('./policy')); loadTestFile(require.resolve('./package')); loadTestFile(require.resolve('./endpoint_authz')); + loadTestFile(require.resolve('./file_upload_index')); loadTestFile(require.resolve('./endpoint_artifacts/trusted_apps')); loadTestFile(require.resolve('./endpoint_artifacts/event_filters')); loadTestFile(require.resolve('./endpoint_artifacts/host_isolation_exceptions')); diff --git a/x-pack/test/security_solution_endpoint_api_int/config.ts b/x-pack/test/security_solution_endpoint_api_int/config.ts index 6e3ca0d718b6e2..505d9734593b95 100644 --- a/x-pack/test/security_solution_endpoint_api_int/config.ts +++ b/x-pack/test/security_solution_endpoint_api_int/config.ts @@ -29,6 +29,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { // always install Endpoint package by default when Fleet sets up `--xpack.fleet.packages.0.name=endpoint`, `--xpack.fleet.packages.0.version=latest`, + // this will be removed in 8.7 when the file upload feature is released + `--xpack.fleet.enableExperimental.0=diagnosticFileUploadEnabled`, ], }, }; diff --git a/x-pack/test/stack_functional_integration/apps/ccs/ccs_console.js b/x-pack/test/stack_functional_integration/apps/ccs/ccs_console.js index 8a86c5fc65f157..08c1f708c063b1 100644 --- a/x-pack/test/stack_functional_integration/apps/ccs/ccs_console.js +++ b/x-pack/test/stack_functional_integration/apps/ccs/ccs_console.js @@ -28,12 +28,12 @@ export default function ({ getService, getPageObjects }) { }); it('it should be able to access remote data', async () => { await PageObjects.console.enterRequest( - '\nGET data:makelogs工程-*/_search\n {\n "query": {\n "bool": {\n "must": [\n {"match": {"extension" : "jpg"' + '\nGET ftr-remote:makelogs工程-*/_search\n {\n "query": {\n "bool": {\n "must": [\n {"match": {"extension" : "jpg"' ); await PageObjects.console.clickPlay(); await retry.try(async () => { const actualResponse = await PageObjects.console.getResponse(); - expect(actualResponse).to.contain('"_index": "data:makelogs工程-0"'); + expect(actualResponse).to.contain('"_index": "ftr-remote:makelogs工程-0"'); }); }); }); diff --git a/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js index 588ff9a6e9f928..39b653784e3314 100644 --- a/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js +++ b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js @@ -113,20 +113,22 @@ export default ({ getService, getPageObjects }) => { expect(patternName).to.be('local:makelogs工程*'); }); - it('create remote data makelogs index pattern', async () => { - log.debug('create remote data makelogs工程 index pattern'); - await PageObjects.settings.createIndexPattern('data:makelogs工程*'); + it('create ftr-remote makelogs index pattern', async () => { + log.debug('create ftr-remote makelogs工程 index pattern'); + await PageObjects.settings.createIndexPattern('ftr-remote:makelogs工程*'); const patternName = await PageObjects.settings.getIndexPageHeading(); - expect(patternName).to.be('data:makelogs工程*'); + expect(patternName).to.be('ftr-remote:makelogs工程*'); }); - it('create comma separated index patterns for data and local makelogs index pattern', async () => { + it('create comma separated index patterns for ftr-remote and local makelogs index pattern', async () => { log.debug( - 'create comma separated index patterns for data and local makelogs工程 index pattern' + 'create comma separated index patterns for ftr-remote and local makelogs工程 index pattern' + ); + await PageObjects.settings.createIndexPattern( + 'ftr-remote:makelogs工程-*,local:makelogs工程-*' ); - await PageObjects.settings.createIndexPattern('data:makelogs工程-*,local:makelogs工程-*'); const patternName = await PageObjects.settings.getIndexPageHeading(); - expect(patternName).to.be('data:makelogs工程-*,local:makelogs工程-*'); + expect(patternName).to.be('ftr-remote:makelogs工程-*,local:makelogs工程-*'); }); it('create index pattern for data from both clusters', async () => { @@ -147,8 +149,8 @@ export default ({ getService, getPageObjects }) => { }); }); - it('data:makelogs(star) should discover data from remote', async function () { - await PageObjects.discover.selectIndexPattern('data:makelogs工程*'); + it('ftr-remote:makelogs(star) should discover data from remote', async function () { + await PageObjects.discover.selectIndexPattern('ftr-remote:makelogs工程*'); await retry.tryForTime(40000, async () => { const hitCount = await PageObjects.discover.getHitCount(); log.debug('### hit count = ' + hitCount); @@ -166,8 +168,10 @@ export default ({ getService, getPageObjects }) => { }); }); - it('data:makelogs-star,local:makelogs-star should discover data from both clusters', async function () { - await PageObjects.discover.selectIndexPattern('data:makelogs工程-*,local:makelogs工程-*'); + it('ftr-remote:makelogs-star,local:makelogs-star should discover data from both clusters', async function () { + await PageObjects.discover.selectIndexPattern( + 'ftr-remote:makelogs工程-*,local:makelogs工程-*' + ); await retry.tryForTime(40000, async () => { const hitCount = await PageObjects.discover.getHitCount(); log.debug('### hit count = ' + hitCount); @@ -176,7 +180,9 @@ export default ({ getService, getPageObjects }) => { }); it('should reload the saved search with persisted query to show the initial hit count', async function () { - await PageObjects.discover.selectIndexPattern('data:makelogs工程-*,local:makelogs工程-*'); + await PageObjects.discover.selectIndexPattern( + 'ftr-remote:makelogs工程-*,local:makelogs工程-*' + ); // apply query some changes await queryBar.setQuery('success'); await queryBar.submitQuery(); @@ -190,7 +196,9 @@ export default ({ getService, getPageObjects }) => { }); it('should add a phrases filter', async function () { - await PageObjects.discover.selectIndexPattern('data:makelogs工程-*,local:makelogs工程-*'); + await PageObjects.discover.selectIndexPattern( + 'ftr-remote:makelogs工程-*,local:makelogs工程-*' + ); const hitCountNumber = await PageObjects.discover.getHitCount(); const originalHitCount = parseInt(hitCountNumber.replace(/\,/g, '')); await filterBar.addFilter('extension.keyword', 'is', 'jpg'); diff --git a/x-pack/test/threat_intelligence_cypress/runner.ts b/x-pack/test/threat_intelligence_cypress/runner.ts index 5ab9d032c55f29..f62544a42456e3 100644 --- a/x-pack/test/threat_intelligence_cypress/runner.ts +++ b/x-pack/test/threat_intelligence_cypress/runner.ts @@ -26,7 +26,7 @@ const retrieveIntegrations = (chunksTotal: number, chunkIndex: number) => { const integrationsPaths = globby.sync(pattern); const chunkSize = Math.ceil(integrationsPaths.length / chunksTotal); - return chunk(integrationsPaths, chunkSize)[chunkIndex - 1]; + return chunk(integrationsPaths, chunkSize)[chunkIndex - 1] || []; }; export async function ThreatIntelligenceConfigurableCypressTestRunner(