diff --git a/.backportrc.json b/.backportrc.json index 94c2549418f17..dbf40e472325e 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 185d8ec09aa5a..f86032c902d1a 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 2a37b65a8cd65..d814ce088e839 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 0a6208c271abf..dd334be41d09b 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 52234380bb0a6..6ee28ad0d4fa6 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 e69cd3fe98487..d7496b410b75e 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 bf12630765b13..96a9d658f0a88 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 ddd56a8934110..008273792d02f 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 d47eb8ba2ab51..daf3593733b1b 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 f82318ef405eb..2cac2f885c74d 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 9e2b34b08519f..2e82b17cdcd7b 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 ce9a5d8ebb782..1fc795de52b86 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 45ad3174d6586..be898589ab710 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 d0119059deb37..43dc0d6a7a65d 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 9f1e5197fe3bc..9f9a248e8ae3d 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 c99cd59b6cb20..a444c90bc5e3d 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 2a59c1d710e97..e041c539996c5 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 b57c31af62e57..a8535f8fa505e 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 aa0f0be527660..c5c0e7694e796 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 9a582352c4ba6..0b60075396e8c 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 3ab49c006af55..f0585d28402ca 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 139cde01e6e82..b4ddda1a26b0b 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 cc4c537605ef6..17db21e2dced9 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 d0b9804daec04..e48fc4111b89a 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 17d55d9e89d55..29be245ff204b 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 d5be614cce150..7f308f473b9bc 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 efdc1b711138b..d43e617968dbe 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 148f2d7770cf2..b77bc0cb5cf7c 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 d97a40733c1c8..9a469733d0ac8 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 cf40c2f308e71..e3377e8649668 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 d72c7980d7fd4..10b91c218240a 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 885597ac5bd37..455b60fc18953 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 12b8cbbd2ad52..a0ff9616b7726 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 241f620bd832b..c6ee25493c618 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 28b1a8eaccd65..1ac5c68c41704 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 863c83640be27..623362e8a45ff 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 991b4113dadb1..2337e17ebb373 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 1a73d13dfac96..248da56308c57 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 a154e3ef0ba36..7a000ab369316 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 50f7bb9384c86..e53643c0b2932 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 92907ba112459..c22f77a8af32b 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 db15923caba01..060c8700b486c 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 77f1ca49c46d5..bfbd30384abb6 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 c6dad24412a01..0604f07043ddc 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 9f14b7670e79b..fbdd11c61ebf2 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 2085d702ea04d..9f0da634f0b0b 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 9f57cc8f2c938..c9d364d62896a 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 4414bde59bbc4..6175ea690c0ac 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 62718e88ba57b..8edea9c7a7ade 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 83c8d73970898..61ea6bb84258c 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 f6691becea10c..81232417be9af 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 7fed1e27b432e..cca3d8fe9cd52 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 9a55aab419ec2..ca4dc26a3fa38 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 0bef7469ac219..8d57e53caaf99 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 0cebc7ce6cc15..ac60ab713419d 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 349834f16f26f..99b592e151bd2 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 1fd9053696fdc..010d8a832b850 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 76b5668bb9c9e..a8b7e9b8ee202 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 b0b39e4dc7d8a..8e1edaf45f978 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 a10589ee7d0be..e1c81c1e2a51b 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 56d41a9d4785d..4a3d3c986d7ad 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 fd4681e96bc92..3ef715b100007 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 0512618f6a86f..c42e3a632aaa8 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 5ccf19338b4c5..795a45ec978a9 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 6a52959f7b552..996ce185bde27 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 64e9a1addcd52..698daaa354917 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 b281e8317bbdc..6ea038e8d71c3 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 f993888be73bf..1d0c78839f7d4 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 52cea44fd2987..5074de04a4788 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 8dbf341970661..dd2b9e910a149 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 2399cdc3be929..bae65c2f28aff 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 24bcd9d9eed2f..962b47f9f2531 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 00ed5db492872..b3bb06a95288c 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 dd9e2918b82b2..170f036cc29e0 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 3bd123d3fa613..eb607098528a3 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 4e1cf1ea5653e..75795af9032c2 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 46cf1a0d04fe3..c858a391f5d4b 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 e371379c6083d..09ae481c1f53a 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 bd114899ad5d0..38161abb3b405 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 3c4bc2a52b387..9770ac6950966 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 169ead73e5162..176c395118782 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 338cdeb28f01e..58b548e40dfdb 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 c0ae1007796da..7f8a662675d11 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 47823b1ff95b4..7655893e5a387 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 2e1927862ade1..ce89b234bfcae 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 af6c812e13dbe..de8c9523b988a 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 56a0068e6c9c0..bd8b5743cc3bd 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 9a0eb5bc0ac7d..8259f7451839f 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 8ebce049520e7..1057f6d50f9fa 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 476a7042823ac..7a55836c7d02f 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 71887b3624822..7c2a41703ae57 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 624830a0a10b5..a1ffcc4c59204 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 ecca931e47544..252b992fa3700 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 c8724df380d18..e59699aabc70a 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 dd3a4d2d02fa8..4d28f60005501 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 0b12a67115327..c65f47ab6849c 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 427f88e52ed37..7ef957525f1c1 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 faf2633501a4b..940c73f0851fc 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 eb0a50865f350..9c7c71f4e6ba2 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 d5f20b16135c8..cad9b679e33a9 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 f39fd94b812aa..8c16689e7539e 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 b691914b4c9a2..9b1c3cc3e9659 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 ff95b1bdcb250..71017c0e8cb5e 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 fb3ed8012a48b..a5a65e9cd7410 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 7b38289ba1f96..8ab5be1e79930 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 a093726cb9788..93fa9807907c3 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 ee2c2fc32664c..3073cbdc2e40d 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 d199a909055ce..15e17ea808e27 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 91eb588a9c54a..ce788965c3190 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 b0ced1c35a7de..1f9e8beb3c160 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 1a18ca462c57e..062565f729f0a 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 7852bf8e4b033..e07bfe1110f2e 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 80b1ee8364315..f2702b2932807 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 3034151db42a8..86192d6885605 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 021592f599c8b..51d5f950187fb 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 01e3743f2f806..e12cd7a4e45f6 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 e4d597db03fb0..866bec323ac72 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 f9da97a2f88f0..ac5213a67915b 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 a306994ce8860..c93797e097c1c 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 149a2f43bb4d1..06e419df0442a 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 882fbfdb27cab..9be90d6462004 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 fcaa49158a216..ea34a3864413d 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 3d6c1f9dba280..c668de3be12b4 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 91840a2cd3028..d0f6dda437736 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 9198c2f610997..fcb86a60ec8f4 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 326b8e4ce3708..145df8e27a8bf 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 2e47c541a8ca8..1070fb91d0a6b 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 2e226792eb92c..83a5af567790e 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 9355e46b4fcb8..7842660014495 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 3b13e7155e092..0b899b43cc4f6 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 ca79600cbe70f..f6c8342ba3932 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 b0754f12d090f..899105a77aa9c 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 66920bcfe22ec..4cb463adcdd57 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 b16f9efeb03e6..20115ee8b28e5 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 d62b32559f477..4685032e14388 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 e800ebdcd6a9e..a9ac87fa6af39 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 645ea01df6af6..c690c64d8941e 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 8391b68a649e8..1e7e233d5f763 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 8b0b18899bfd8..f756eec44ccad 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 bdb1771e932db..3b3effe5bc568 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 9d18e86bbd82e..ce5a3ca8c9153 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 ba97dab000eaf..fadd15215af3a 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 5cf14b018a8c8..20fa60516b7ee 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 88a4617ea6438..e091f7257df05 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 30b1f5d199eb7..b40a716679db7 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 db346176fd58c..c07a52848728a 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 79da72b47d072..dcf8a48100ec5 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 9f7b7a227c4d6..ebb45627cacbd 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 a014bbb2ba166..6b66243ae049c 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 c871b7219a2af..1bbb0e1062a04 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 ad52c001d9fa3..089d2cbd3cf82 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 fea73633c7500..13744384a2a4e 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 4d44fa011b05b..d0ce309fab234 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 d398d2443b9b3..1bddac22ab27e 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 7cb081220b524..24fac8e625e5c 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 60b3afdfd9f61..86209a465666d 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 23a10ff59402a..2c37e9d6fd503 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 24e6ddbbccbc6..611c97bf40129 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 fd75e41f35e4b..e3d919e8913f9 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 9a0ea560091e4..efb662eac45e8 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 a677aae3dbe81..2053ea0bb6081 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 7d8239af35ef2..84c01942bfe21 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 0b60d81f76f70..86daad0ed2067 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 2c1029f520969..0be0c484754c2 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 24ca177cd1667..526ae6bf5d2bc 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 a5d41f2b2f9d5..a8c02421a5830 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 f2027b3b03189..1a37bf3341402 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 6f0a3dd0f808a..e30d00cbccd3d 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 3931cf5c608d6..d043a7b7b8cba 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 efc968da78673..7a3f0e597c61b 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 945b57424b0e5..fe97a5f3d4189 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 cff3ead11d2c3..2dffa64ef5dc8 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 c6c61c40b4b85..5f661d56189a1 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 bcfdc7b9ca7a7..d2a672611858b 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 4ff6bf3168a86..6da3fa1c63a8d 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 bbaa67443d3b0..a5fe3b48eccd5 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 2e663e5e4dbd3..16747bbbc0da7 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 260206897dda1..30e07115f0009 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 514c93817abab..9c9a20135f954 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 6f99af314013e..2d427ae59e9c2 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 f5de18f258d42..a984578e62443 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 ffd42e9ef360f..b30331b3ebd13 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 57c436f76bee4..4b9ff78723d1f 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 e0edc2f97638f..4699fe72ed84e 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 5d28a1f1f6b05..402850e5738ad 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 1f276c599f753..66b661f0a08e0 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 ed4647e3f14ee..7e5fba8b4b80f 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 75d925ded836a..d484b5fb16cd1 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 f9aa251c8a768..b53d88112c2aa 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 94999ffaa469e..2e59594612d82 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 14512b85e78bf..2fcdd363a2e03 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 a5cbf55f24e3b..312141a828d3c 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 53f053fa4bd45..9de9140c7a431 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 a51c41f5c9d85..30a868e31e8dc 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 027a244329c9d..b8410474cc4d3 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 06090277c1e1e..8ed8eaa3f33a7 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 716b18271a51b..be70b3b7913a6 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 6134732c2a134..32b979411dbe1 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 b70ba21fa082d..cf3ce500ebe06 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 1b6539896fac7..26721a68e2053 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 d13d8a9d8ee47..3204bd9da3cae 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 10a71f3a867f7..9f1ec84e17525 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 16479f4d07c47..313a2089a1986 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 06eaa4a9be8ee..41ab447bfc36c 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 d921b764a7bd2..0385a59ff88e4 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 08a657c0d7a38..79c56bd950e19 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 5c4544bb4de34..3a7289c8b12f5 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 e2998c71b6546..f9b8781a8b73b 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 077a98f7b67da..2ba74fb5f6c73 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 e08f8ad52fa57..bb3093cb556d8 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 98be907750376..87252570e61fd 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 94471bd846ce1..131cfff7e534e 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 830327ee2060b..d988c16a37805 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 850719ba262d2..2ad924c3302b0 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 f910f3e246341..23bef83b664b0 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 3dd9be91d7a08..b21b676f8d005 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 ec74b58d591be..5f629cf9b2e70 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 e0ac33ca90b38..f722715b6a6d7 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 1fea9e6b6399e..e7ac6fbf6a1a3 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 0000000000000..8dff7846c2e91 --- /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 0000000000000..fe38ff36d74d0 --- /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 6f80c1eff962a..903922b168243 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 25323964f9dba..3003009d581e0 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 da5715646c24d..f79ca8d1898ba 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 60a83b644e2c0..2d4acc808f6b6 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 26f3b707d86eb..ff1db6d53d7ea 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 61a4ee4ce62ed..546cd50bfc7a8 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 a3b3416d2846d..19fa1d8d63637 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 c31a5eb11e0b8..e771a66f60631 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 084233b1249f7..714fe3ca5cd6a 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 813a8e4e76d84..0b0cc31513703 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 93d3abc89e2ec..2e22ac78092b4 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 7025e70531170..69a889b4fd9fb 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 859aca01f4dc7..8f4cbf660bc5b 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 b32669f0c3404..1b94ab75206a7 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 e700fab4ac171..b7fe92a8771fe 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 5c05ae919064e..3ace085023828 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 cf27e39d0bbee..85d24a3beab12 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 38665686483d3..42d8cc447c93f 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 08dca8dfddd80..7f65d3efd2c4f 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 8556eab16c9bb..67ab063b9d7ec 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 ffaa723524e7c..eb6bcf0fb8cc4 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 14d27a3ecf1d4..4585d0ed05248 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 f31c78cddc8a8..938098f1d5e01 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 748cc0c1e06d9..89742770e0f5c 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 827b3e2dc07d5..93cd3e4f85d86 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 1be443835725a..48180e68e793c 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 9c501b111ff6b..3dde0c3ad41a9 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 b7aca3a0f84a5..eff5424c88eb7 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 bdac11a231449..3bccf6252af92 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 9c13fe143e580..aea3f73233db2 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 dadee518c3493..53ca097a6b2be 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 52b9f7bb5a296..ffcf86d74bef5 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 592e13dab8cd6..0031eabf7d29c 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 d7f5bf230a3c0..a64217aff405a 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 c8338e1d385fc..41b4cee5b2b85 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 3684e26236900..7f419e90a404f 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 6f676d6365f9e..6759ec9d4d0d1 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 e3503599f872f..63b49605babf3 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 999169677a7a0..c3264ab950639 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 ef18ad95e93cc..87b70692aae1d 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 e32f9e4dff9e2..d31e95455de92 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 8b537e68c58b0..3e363267f6a8d 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 60826fb35177c..91612423d9707 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 3666d84a9892d..8fd83ee2492a5 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 7dbfcc173d281..29faf402e97cf 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 6526d82796129..98465d44b7f86 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 310c67b5528d5..08c36b54078b1 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 d4b45849cae6b..6c695219b8273 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 59846f75e673f..50c2d3b0fed7a 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 4f03feb5023ab..e6f4520a7af2f 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 23df16db144c3..dcb4a3db01bdf 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 3bada57b27b46..fb7a12df900c3 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 62a61081226ee..94810796a56da 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 68afa212fc701..c6e29fa674d0c 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 58154a74e1ba7..d5378d7d03785 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 5be762cb15a51..823eb0a7d69ea 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 18b24b7afe3d4..c3d48d1541541 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 d1b8920d30bc4..9959c175abc51 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 0c09d56e0d863..b49e9a1d68e20 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 4060b6f4185db..ba6412a42b1fb 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 a7b39f97ca574..2793921c796a2 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 c5a22e34b88ab..959997f6373fd 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 9b7a2f65f640a..70a165d7dfb6a 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 bdec839c63fc0..3d6c15344db48 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 33d581b6b32d9..46bee503a59c1 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 55ed7dde39cc0..3f6b6a4baf8fd 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 89f5e5c0e08e4..70ed54a483679 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 2c44f793632f5..1ff30e5fd4bce 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 b721a20f6155b..6841153c8097d 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 959056a931bc6..e476eda27ab48 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 9bd07ae7ceb1c..6fe073b46c4c7 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 8472723b82cab..7d8d9cda7da4e 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 794d0f6119edc..19d69e7a9750d 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 770e89253cdaa..da449f8b59ff9 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 a0834fda9c049..c45b791157433 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 bbebfccd36e4f..7d501dd18eaf0 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 0e52c94b1a97f..f7d79e631dc49 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 8e3c1842b55e1..459bd08caa235 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 852a3f17833b6..90efd4b0a86bd 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 a70d9b4d11c3e..c09fa9df42841 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 c429db6293cf1..b1a43d05ab4cd 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 dbcfe068bd445..8d1c611fbe8ad 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 007fc573b4940..98945a37a3b4b 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 0009ca71c1073..58bc9b94b531e 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 ed63d11aac696..6eabb3128c34d 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 a4791494ac4e0..0c34416cae8cd 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 a3b504eca02bf..d1fed13cc4f16 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 38697c001c26b..fae0f3ea5858b 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 555242b1a4a4e..719ae39b4667b 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 2787272789786..13c2bffd85cc5 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 17f6631bd21e1..f023ad5e53bc8 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 183c3c7340088..cb30a880c9d5b 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 1bb8c0b839898..6e5e61be34b89 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 06d16e852ab7c..be837367237a3 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 0102cdfb7cf63..770dee65dff98 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 530ecfe512c2d..3a4304ed84385 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 3deae01684888..a8bbf2df0d1fd 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 67bd54674623d..48ffb91be1319 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 53d36b0b36666..e4f1d9abe3a18 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 316ed6303b682..81efaf0dccb2a 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 4b0660f89c540..884f4ef008085 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 88f0358db53d4..a7c37c9e05b8a 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 a98382642240c..633f7bc9c9b96 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 08b5d8aeaae11..e7a04508a0af0 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 0a9c5e43f0363..6e3b4956953d4 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 16a23d7b3e4ce..acebb4182e77e 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 152e84349149b..dc2ae8f1e1500 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 bd8e176120b92..cfcee7e7f0594 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 4d4136d67c60d..cd8334e742dda 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 86eacb230997e..2109c2452185f 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 887c3fde332e8..7e1ef0bca3478 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 67eade2f7b66f..3282b2dfbadd4 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 100d4f6358f42..c8577f775f164 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 8cd1856c58b84..87e026610f505 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 c52fd4384ee85..80a0723b1af6b 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 a861809783d4a..3ffe888e1db9f 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 ad21768895ac4..33acdd7b9d352 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 fd05a89b0a3d6..65b8fcaee2e78 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 ce63b5c304418..bab2e1c7af40c 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 56564290d670a..4eecc315e8476 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 6d45ffd6833cd..e1a0a2f8ff6bd 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 538ac53a71469..6ea0f42746bb4 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 4cdf187c9b97c..07f0dcfdeab85 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 b05d5daae819d..a2ae7e127b0dc 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 5957a0d4eef25..794cb2a7521c5 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 f87fec89c1125..7f59580b891e1 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 7cee906113399..88fbd1f27377d 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 ae6608a7a7f6a..9dff8f073ce55 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 2073acb2e8c79..3da056d5baf19 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 ec03f331f8cea..e1fbb95ca482c 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 a0176dc741e7a..c2cb3338f30ae 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 a79c63f491464..0d83bd4855798 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 a43a5fc47ddb3..16847e487b12b 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 d745aa5def7fb..5b46aa267f8ac 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 5398485a7a739..dd15142536e2c 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 f4b25d9624c6b..6ca8e0be79819 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 d02f42feead44..0653d0d0e8761 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 b53245e184c95..4f3497b0138b9 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 f5eb6d71a6d4c..890659d31bbab 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 5533ca8b9d872..eade29ceeaed2 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 233f40076c8a8..772c8259aec02 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 383176e681777..2cc4c323c3166 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 93c1bb9a09832..bf95a589b530f 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 e74913881777c..eaaebcf1aa7ac 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 5f358055cb2f7..e415071f185fd 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 a434788cc68d0..5153b48826528 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 59ea5ecfd1754..45e840b91afae 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 3f5b5fa154a06..69c2c14ca101b 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 ae0aa8e37b769..e2d49b1ddb40d 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 bd1af52f31a1d..f6f42c6757d9b 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 27431c65582a1..4f825cd655945 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 188721b09a6ed..7cb0df660d7f9 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 b13de4a9bc408..af5b3ab80fa29 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 fd8d8f26c09b4..f3797a34e392c 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 91d81ffdf47a1..7a0d9e0bfc867 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 64e8dcc18d3d4..0549cce0d49c5 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 883abd50b1fbf..d66f0d86e41ae 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 cf7c5ef660aef..2b09a050921c3 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 314a884b56b23..08a35774b1713 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 1f7082bbd90e0..a537411737add 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 172619dacbc5f..baf48eec4f2ff 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 af72483ba3318..1b5b7a6c08eb5 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 a80b63bbb023c..87d4df0ed1ca4 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 4c58e2386d38c..171f91d6281fb 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 a7fce86ff7664..47b5e5d465f08 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 8ae1d10084e78..df473b54dcdc2 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 fb73f0afd1598..a5319befe9f12 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 c7b23b6cc5f9c..906d1bfae133b 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 ab60c57a3a277..1e70ec71d7105 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 4bd8243c7322b..711af9895ec4a 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 c48961d89d73a..542b9509d3c46 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 a897b433125e8..329c522bb7c80 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 01bce25eca59d..964cba8aadadc 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 42d9a2edb2f34..9424efd6a2143 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 f581582c7c99a..fccf2f8a0eed4 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 2ba0788fa7996..bdf0bf9bf9919 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 5dbc68bdd71e6..07690d8820e41 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 3b223dfc8b40e..4fec68fbce9e2 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 ea176fe9e1bf9..50291bc74fdb9 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 55ce2617607f9..2975c91344f29 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 dfb0abe618ff9..e3ecc65c75c85 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 6ec80f478a515..d44392aec4243 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 3d637e56b4151..ec9e8fe855ed4 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 4e23f09f052bd..ad6573695819d 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 7f9a741419db9..bcc3424bd50b5 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 a35a49df23854..1e10064fac939 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 c1859102da832..dbca2b7c0ba3a 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 fc071fd94928a..290559a5f1259 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 18cdb1a5b2aa0..11a755903bfba 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 44066209bfabc..69e43cef179ca 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 d869c6c6d13de..cf3480924f3ac 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 9def8a31f48b1..0d1c81fb78293 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 4b7df010df8c3..3eedd5db5db3c 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 d8fbd90585a28..2f78e8f71305e 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 7c02ccf9c73ee..d1a5f094f4542 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 bca458841e97f..6556afd417cc6 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 df1ce0f82841f..c59831e65bd8f 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 fc82c45ca6829..82c7c76427b47 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 794436cc975e3..564230cf4acec 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 8e8f5973908a6..c02074a60d268 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 2a600480640eb..ad2e887fe4838 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 9ce702a38240e..e109ed5e4d292 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 93f254fa785ee..06db4d41eb53f 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 fd079df9d0332..5c9c7860818e4 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 4d3db42d2959e..0b186ffadd157 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 180914e3e145f..1675003df6752 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 ac155061381cc..54015f219b082 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 5fd7d04345ae4..f2a051b7671ae 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 1ec9d09a7bebc..18029824f8182 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 36a666cbb6ef1..7eb3f18ac7096 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 e5e0d88a7bd9c..236df7049d83c 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 cb96880b175ef..c6e1ad8190141 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 a0b9bf9d69a8d..774148e0da83f 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 49b03d166a147..961dd71a56f20 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 7ac2147b85cde..eb59f546d51e1 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 8a33ceafa4f2c..5aee943618d19 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 e30edf33df59e..adbbed8269b70 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 71e244c15c41d..e20ca0b85380e 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 564a683059230..1504f87a7927b 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 6899f5644f04a..849d6e3eb3f8a 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 2838c161fb042..0781dcb18634b 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 3e8b61b7e2a1d..439ee24c75902 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 5b46b0e4ff91e..8c7ffc1a922d6 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 3b89fe1e05c71..9e24afbbc1f9c 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 24cb838c2f1fe..8aa1fa9b5b6e8 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 fd5fe8068cb82..4b5a4a858f6b9 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 8a329334423a9..7fe56c56463e6 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 7a7b65070d2d1..e30325af6a671 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 035bcd35366b0..bc8d21b8832ec 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 da491994a1ec2..c3cdedce63d86 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 66e6cb18a7a1d..ed584fd9352f9 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 14896e2e9e2b0..6c5c9334c4d59 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 a6cb7fc7df62e..e2148de475157 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 2434cfac2a6d7..5a94f2bda5c93 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 e8b3b304ae595..35bbdde254eeb 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 2c0e4c76e2237..f2f1cda8132a5 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 52869c4dababd..ae5d7e342b449 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 bea702c5de6bd..f1e69211deb5e 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 4a391e98fe3f3..5456b143ed9b9 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 8bfe264657560..27748e7d6a5bf 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 0c474cc7d488b..ebc6f3501c17d 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 d8894f1e8f933..04ea33fbf56c0 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 ab5ec8b48b146..fc78738d71370 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 86bcf1e39059f..ce3ebe28bb6f4 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 36106b22d469f..e02fe4c1922b7 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 e4351ff278d33..dbf1636138493 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 5a315366f2127..dfdab920c276a 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 253f544616f1b..85e8dd1bb3f25 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 d77468762eca4..8605cf20dbc9a 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 92e0c892b1d9f..db2b89eba9c69 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 f19ead519a07a..b5ce4115e0bd8 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 d31bb4f8807e8..e498e7ab1be33 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 c33cbdd77232c..b277a49d43723 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 333a06cf3754e..91b7ae349b9a8 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 0000000000000..8066736c0c15d --- /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 0000000000000..dec846fd6fee0 --- /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 c3f8239e9af91..c610c787d2b8d 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 b8d50719a6648..9aa871ebf334b 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/management/connectors/action-types/tines.asciidoc b/docs/management/connectors/action-types/tines.asciidoc index f8080be8edda5..b9060943adf57 100644 --- a/docs/management/connectors/action-types/tines.asciidoc +++ b/docs/management/connectors/action-types/tines.asciidoc @@ -13,12 +13,12 @@ The Tines connector uses Tines's https://www.tines.com/docs/actions/types/webhoo Tines connectors have the following configuration properties. -URL:: The Tines tenant URL. If you are using the <> setting, make sure the hostname is added to the allowed hosts. +URL:: The Tines tenant URL. If you are using the <> setting, make sure the hostname is added to the allowed hosts. Email:: The email used to sign in to Tines. -API Token:: A Tines API token created by the user. https://www.tines.com/api/authentication#generate-api-token[Docs] +API Token:: A Tines API token created by the user. For more information, refer to the https://www.tines.com/api/authentication#generate-api-token[Tines documentation]. [role="screenshot"] -image::../images/tines-connector.png[Tines connector] +image::management/connectors/images/tines-connector.png[Tines connector] [float] [[Preconfigured-tines-configuration]] @@ -57,30 +57,30 @@ Webhook:: The Webhook action from the previous story that will receive the event Test Tines action parameters. [role="screenshot"] -image::../images/tines-params-test.png[Tines params test] +image::management/connectors/images/tines-params-test.png[Tines params test] [float] [[tines-action-format]] === Actions -Once the Tines connector has been configured in an Alerting Rule. +Once the Tines connector has been configured in an alerting rule: [role="screenshot"] -image::../images/tines-alerting.png[Tines rule alert] +image::management/connectors/images/tines-alerting.png[Tines rule alert] -It will send a POST request to the Tines webhook action on every action execution with at least one result. +It will send a POST request to the Tines webhook action on every action that runs with at least one result. [float] [[webhookUrlFallback-tines-configuration]] ==== Webhook URL fallback -It is possible for the requests to the Tines API, to get the stories and webhooks for the selectors, to hit the 500 results limit; in this scenario, the webhook URL fallback text field will be displayed. -Users can still use the selectors if the story or webhook exists in the 500 options loaded. Otherwise, users can paste the webhook URL in the test input field, it can be copied from the Tines webhook configuration. +It is possible that requests to the Tines API to get the stories and webhooks for the selectors hit the 500 results limit. In this scenario, the webhook URL fallback text field will be displayed. +You can still use the selectors if the story or webhook exists in the 500 options loaded. Otherwise, you can paste the webhook URL in the test input field; it can be copied from the Tines webhook configuration. -When the webhook URL is defined, the connector will use it directly in the execution stage, and the story and webhook selectors will be disabled and ignored. To re-enable the story and webhook selectors, remove the webhook URL value. +When the webhook URL is defined, the connector will use it directly when an action runs, and the story and webhook selectors will be disabled and ignored. To re-enable the story and webhook selectors, remove the webhook URL value. [role="screenshot"] -image::../images/tines-webhook-url-fallback.png[Tines Webhook URL fallback] +image::management/connectors/images/tines-webhook-url-fallback.png[Tines Webhook URL fallback] [float] [[tines-story-library]] @@ -90,7 +90,7 @@ In order to simplify the integration with Elastic, Tines offers a set of pre-def They can be found by searching for "Elastic" in the Tines Story library: [role="screenshot"] -image::../images/tines_elastic_stories.png[Tines Elastic stories] +image::management/connectors/images/tines_elastic_stories.png[Tines Elastic stories] They can be imported directly into your Tines tenant. @@ -98,7 +98,7 @@ They can be imported directly into your Tines tenant. Tines connector will send the data in JSON format. -The message contains execution specific fields, such as `alertId`, `date`, `_index`, `kibanaBaseUrl`, along with the `rule` and `params` objects. +The message contains fields such as `alertId`, `date`, `_index`, `kibanaBaseUrl`, along with the `rule` and `params` objects. The number of alerts (signals) can be found at `state.signals_count`. diff --git a/docs/management/connectors/images/connector-delete.png b/docs/management/connectors/images/connector-delete.png index 2e0e5d8a06b25..0791bb86b5e31 100644 Binary files a/docs/management/connectors/images/connector-delete.png and b/docs/management/connectors/images/connector-delete.png differ diff --git a/docs/management/connectors/images/connector-filter-by-type.png b/docs/management/connectors/images/connector-filter-by-type.png index c09a285d2af9c..b9383c189256d 100644 Binary files a/docs/management/connectors/images/connector-filter-by-type.png and b/docs/management/connectors/images/connector-filter-by-type.png differ diff --git a/docs/management/connectors/images/connector-listing.png b/docs/management/connectors/images/connector-listing.png index e7fb0899ef4a7..52217b31331f2 100644 Binary files a/docs/management/connectors/images/connector-listing.png and b/docs/management/connectors/images/connector-listing.png differ diff --git a/docs/management/connectors/images/connectors-with-missing-secrets.png b/docs/management/connectors/images/connectors-with-missing-secrets.png index f4f2ba0d73e13..e20973b5640de 100644 Binary files a/docs/management/connectors/images/connectors-with-missing-secrets.png and b/docs/management/connectors/images/connectors-with-missing-secrets.png differ diff --git a/docs/management/connectors/images/pre-configured-connectors-managing.png b/docs/management/connectors/images/pre-configured-connectors-managing.png index e41b89b331007..b7fe88ccf2fa9 100644 Binary files a/docs/management/connectors/images/pre-configured-connectors-managing.png and b/docs/management/connectors/images/pre-configured-connectors-managing.png differ diff --git a/docs/management/connectors/index.asciidoc b/docs/management/connectors/index.asciidoc index f6a605cf491c0..fe65120e4b2b9 100644 --- a/docs/management/connectors/index.asciidoc +++ b/docs/management/connectors/index.asciidoc @@ -14,5 +14,5 @@ include::action-types/webhook.asciidoc[] include::action-types/cases-webhook.asciidoc[leveloffset=+1] include::action-types/opsgenie.asciidoc[] include::action-types/xmatters.asciidoc[] -include::action-types/tines.asciidoc[] +include::action-types/tines.asciidoc[leveloffset=+1] include::pre-configured-connectors.asciidoc[] diff --git a/docs/maps/asset-tracking-tutorial.asciidoc b/docs/maps/asset-tracking-tutorial.asciidoc index 4e6efff35b3a3..f33ce2ef7547e 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 9e1ee62f093fe..f459150b0ee8d 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 9d254ce5fc834..62604c465dafc 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/alerting-overview.png b/docs/user/alerting/images/alerting-overview.png index 7a4124f1b0377..b9368f737b550 100644 Binary files a/docs/user/alerting/images/alerting-overview.png and b/docs/user/alerting/images/alerting-overview.png differ diff --git a/docs/user/alerting/images/individual-enable-disable.png b/docs/user/alerting/images/individual-enable-disable.png index 0cdac4d9b526a..cca96a119da3b 100644 Binary files a/docs/user/alerting/images/individual-enable-disable.png and b/docs/user/alerting/images/individual-enable-disable.png differ diff --git a/docs/user/alerting/images/rules-and-connectors-ui.png b/docs/user/alerting/images/rules-and-connectors-ui.png index f4228200f8bde..ad04bfbc470ee 100644 Binary files a/docs/user/alerting/images/rules-and-connectors-ui.png and b/docs/user/alerting/images/rules-and-connectors-ui.png differ 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 ffdac4fcd1983..0000000000000 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 e81c4e07dd7b2..edd16b245ec65 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/docs/user/alerting/images/snooze-panel.png b/docs/user/alerting/images/snooze-panel.png index fedd86191fd3b..79b64b914338d 100644 Binary files a/docs/user/alerting/images/snooze-panel.png and b/docs/user/alerting/images/snooze-panel.png differ diff --git a/examples/field_formats_example/tsconfig.json b/examples/field_formats_example/tsconfig.json index 66e9d7db028c7..a7651b649e5b3 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 2ce0ddb8f7d66..9329f941c1006 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 f074171954048..6cfb28f7b3317 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 ba03cbc836189..97d4c752cc3b5 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 17222ef4a5266..4c8bcb3cd3192 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 7ad2173ff9f15..236ac8087855b 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 f5cc0d5efa889..e51795faa6a03 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 601f94aa1e243..efe32683ee468 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 57741f098c6ac..9e0c559cbdc55 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 221ca0ff3872f..d09bfa67dee82 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 9a25f821b70a3..5726bf0046687 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 4660ae9d27e72..fccdd4788f7d9 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 67fe2c54bd77e..c1e2ab8a81153 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 47728a99a511a..e82ff3c45b1fa 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 53f19910eab75..185ce37072be0 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 091be6cb96e83..b9002892ec53e 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 34ebe134adcf7..cb6e689dd893f 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 0bf00e91c7d0e..e60686937884a 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 a12f373fcd388..157cfaee22f0c 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 92e1ddaa45cc0..d60530b014be5 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 3d98cf4392926..7f32ea7ed41a6 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 0dcd49bd69fcc..60656e9dfd1cb 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 2484f37bbf563..331f1695d9f20 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 e18a5faba8fbc..dbc35043613cd 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 46b0726660e4c..141f5b9970c0b 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 1b297b197374d..4879b988b1752 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 b642c505cad38..32245c6e9f61c 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 22be209158b89..13bd334148ae5 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 11e034a56914b..3fe9c8ac727ff 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 bb260ae23c908..18e6a21ed2dba 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 d4ae597c953ff..4cdfabddf2697 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 49d77b7882f4a..f93a80f82d06e 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 ac1bcb02a0349..3e9e25e790b47 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 ddf6215aaba90..897e5609a8347 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 0000000000000..49ba239829b24 --- /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 ae37273c8aefb..6b51906cca1ef 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-exception-list-components/src/exception_item_card/meta/meta.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.test.tsx index 38ec3f7623aa5..9fb071f80722b 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.test.tsx +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/meta.test.tsx @@ -79,7 +79,7 @@ describe('ExceptionItemCardMetaInfo', () => { item={getExceptionListItemSchemaMock()} rules={[ { - exception_list: [ + exceptions_list: [ { id: '123', list_id: 'i_exist', @@ -98,7 +98,7 @@ describe('ExceptionItemCardMetaInfo', () => { rule_id: 'rule-2', }, { - exception_list: [ + exceptions_list: [ { id: '123', list_id: 'i_exist', diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_items/index.tsx b/packages/kbn-securitysolution-exception-list-components/src/exception_items/index.tsx index 647ff3a14458a..840a491eaad14 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/exception_items/index.tsx +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_items/index.tsx @@ -104,7 +104,7 @@ const ExceptionItemsComponent: FC = ({ css={exceptionItemCss} data-test-subj={`${dataTestSubj || ''}exceptionsContainer`} direction="column" - gutterSize="m" + gutterSize="s" > {exceptions.map((exception) => (
-
+
- + @@ -386,9 +386,7 @@ Object {
, "container":
-
+
- + @@ -668,9 +668,7 @@ Object { "asFragment": [Function], "baseElement":
-
+
- + @@ -893,9 +893,7 @@ Object {
, "container":
-
+
- + @@ -1175,9 +1175,7 @@ Object { "asFragment": [Function], "baseElement":
-
+
- + @@ -1373,9 +1373,7 @@ Object {
, "container":
-
+
- + @@ -1628,9 +1628,7 @@ Object { "asFragment": [Function], "baseElement":
-
+
- + @@ -1765,25 +1765,6 @@ Object { Linked to 0 rules
-
- -
@@ -1906,9 +1887,7 @@ Object {
, "container":
-
+
- + @@ -2043,25 +2024,6 @@ Object { Linked to 0 rules
-
- -
diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/index.tsx b/packages/kbn-securitysolution-exception-list-components/src/list_header/index.tsx index 122b38cb9bb6a..95546ee84eaf5 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/index.tsx +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/index.tsx @@ -10,7 +10,7 @@ import React from 'react'; import type { FC } from 'react'; import { EuiIcon, EuiPageHeader, EuiText } from '@elastic/eui'; import * as i18n from '../translations'; -import { textCss, descriptionContainerCss, headerCss } from './list_header.styles'; +import { textCss, descriptionContainerCss, backTextCss } from './list_header.styles'; import { MenuItems } from './menu_items'; import { TextWithEdit } from '../text_with_edit'; import { EditModal } from './edit_modal'; @@ -25,7 +25,7 @@ interface ExceptionListHeaderComponentProps { isReadonly: boolean; linkedRules: Rule[]; dataTestSubj?: string; - breadcrumbLink?: string; + backOptions: BackOptions; canUserEditList?: boolean; securityLinkAnchorComponent: React.ElementType; // This property needs to be removed to avoid the Prop Drilling, once we move all the common components from x-pack/security-solution/common onEditListDetails: (listDetails: ListDetails) => void; @@ -34,6 +34,12 @@ interface ExceptionListHeaderComponentProps { onManageRules: () => void; } +export interface BackOptions { + pageId: string; + path: string; + dataTestSubj?: string; + onNavigate: (path: string) => void; +} const ExceptionListHeaderComponent: FC = ({ name, description, @@ -42,7 +48,7 @@ const ExceptionListHeaderComponent: FC = ({ isReadonly, dataTestSubj, securityLinkAnchorComponent, - breadcrumbLink, + backOptions, canUserEditList = true, onEditListDetails, onExportList, @@ -55,7 +61,7 @@ const ExceptionListHeaderComponent: FC = ({ onEditListDetails, }); return ( -
+
= ({ breadcrumbs={[ { text: ( -
+
{i18n.EXCEPTION_LIST_HEADER_BREADCRUMB}
), color: 'primary', 'aria-current': false, - href: breadcrumbLink, - onClick: (e) => e.preventDefault(), + href: backOptions.path, + onClick: (e) => { + e.preventDefault(); + backOptions.onNavigate(backOptions.path); + }, }, ]} /> diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/list_header.styles.ts b/packages/kbn-securitysolution-exception-list-components/src/list_header/list_header.styles.ts index 4ed5e9dbb63cf..e216a4d538bf2 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/list_header.styles.ts +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/list_header.styles.ts @@ -9,10 +9,6 @@ import { css } from '@emotion/react'; import { euiThemeVars } from '@kbn/ui-theme'; -export const headerCss = css` - margin: ${euiThemeVars.euiSize}; -`; - export const headerMenuCss = css` border-right: 1px solid #d3dae6; padding: ${euiThemeVars.euiSizeXS} ${euiThemeVars.euiSizeL} ${euiThemeVars.euiSizeXS} 0; @@ -31,3 +27,7 @@ export const descriptionContainerCss = css` margin-top: -${euiThemeVars.euiSizeXXL}; margin-bottom: -${euiThemeVars.euiSizeL}; `; + +export const backTextCss = css` + font-size: ${euiThemeVars.euiFontSizeXS}; +`; diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/list_header.test.tsx b/packages/kbn-securitysolution-exception-list-components/src/list_header/list_header.test.tsx index 23dd5e849f0b7..90ce10236fa6d 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/list_header.test.tsx +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/list_header.test.tsx @@ -16,6 +16,7 @@ const onEditListDetails = jest.fn(); const onExportList = jest.fn(); const onDeleteList = jest.fn(); const onManageRules = jest.fn(); +const onNavigate = jest.fn(); jest.mock('./use_list_header'); describe('ExceptionListHeader', () => { @@ -39,6 +40,7 @@ describe('ExceptionListHeader', () => { onExportList={onExportList} onDeleteList={onDeleteList} onManageRules={onManageRules} + backOptions={{ pageId: '', path: '', onNavigate }} /> ); expect(wrapper).toMatchSnapshot(); @@ -68,6 +70,7 @@ describe('ExceptionListHeader', () => { onExportList={onExportList} onDeleteList={onDeleteList} onManageRules={onManageRules} + backOptions={{ pageId: '', path: '', onNavigate }} /> ); expect(wrapper.queryByTestId('RightSideMenuItemsMenuActionsButtonIcon')).toBeEnabled(); @@ -90,6 +93,7 @@ describe('ExceptionListHeader', () => { onExportList={onExportList} onDeleteList={onDeleteList} onManageRules={onManageRules} + backOptions={{ pageId: '', path: '', onNavigate }} /> ); expect(wrapper).toMatchSnapshot(); @@ -119,9 +123,35 @@ describe('ExceptionListHeader', () => { onExportList={onExportList} onDeleteList={onDeleteList} onManageRules={onManageRules} + backOptions={{ pageId: '', path: '', onNavigate }} /> ); expect(wrapper).toMatchSnapshot(); expect(wrapper.getByTestId('EditModal')).toBeInTheDocument(); }); + it('should go back the page path when back button is clicked', () => { + (useExceptionListHeaderMock as jest.Mock).mockReturnValue({ + isModalVisible: true, + listDetails: { name: 'List Name', description: 'List description' }, + onSave: jest.fn(), + onCancel: jest.fn(), + }); + const wrapper = render( + + ); + fireEvent.click(wrapper.getByTestId('Breadcrumb')); + expect(onNavigate).toBeCalledWith('test-path'); + }); }); diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/__snapshots__/menu_items.test.tsx.snap b/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/__snapshots__/menu_items.test.tsx.snap index be9608a44a7c7..0dc8f79ac2d5d 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/__snapshots__/menu_items.test.tsx.snap +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/__snapshots__/menu_items.test.tsx.snap @@ -1,5 +1,214 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`MenuItems should not render Manage rules 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[`MenuItems should not render linkedRules HeaderMenu component, instead should render a text 1`] = ` Object { "asFragment": [Function], @@ -509,25 +718,6 @@ Object {
-
- -
@@ -685,25 +875,6 @@ Object {
-
- -
diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/index.tsx b/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/index.tsx index bd043af4b1550..788c92e35970e 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/index.tsx +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/menu_items/index.tsx @@ -72,18 +72,19 @@ const MenuItemsComponent: FC = ({ )} - - { - if (typeof onExportList === 'function') onManageRules(); - }} - > - {i18n.EXCEPTION_LIST_HEADER_MANAGE_RULES_BUTTON} - - - + {canUserEditList && ( + + { + if (typeof onExportList === 'function') onManageRules(); + }} + > + {i18n.EXCEPTION_LIST_HEADER_MANAGE_RULES_BUTTON} + + + )} { expect(wrapper).toMatchSnapshot(); expect(wrapper.getByTestId('MenuActionsActionItem2')).toBeDisabled(); }); + it('should not render Manage rules', () => { + const wrapper = render( + + ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.queryByTestId('ManageRulesButton')).not.toBeInTheDocument(); + }); it('should call onManageRules', () => { const wrapper = render( linkedRules: [ ...rules, { - exception_list: [], + exceptions_list: [], id: '2a2b3c', name: 'Simple Rule Query 2', rule_id: 'rule-2', diff --git a/packages/kbn-securitysolution-exception-list-components/src/types/index.ts b/packages/kbn-securitysolution-exception-list-components/src/types/index.ts index ba35f0753cc68..377779332c869 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/types/index.ts +++ b/packages/kbn-securitysolution-exception-list-components/src/types/index.ts @@ -60,7 +60,7 @@ export interface Rule { name: string; id: string; rule_id: string; - exception_list?: ListArray; + exceptions_list?: ListArray; } export interface RuleReference { 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 ad84d295cff84..63711c8a036bd 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 8d23db33601a4..440217ff65167 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 c73405f1950b8..876b236004a77 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 2833d9f0bad13..239350747c7fd 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/multiple_kibana_nodes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/multiple_kibana_nodes.test.ts index e6d74c28efb8c..73d22b8aba5a9 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 32245e26c69ec..c148cccfa7351 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 9156c52a23d01..c4998e6791957 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 6f08439797e88..30fb883c9d15f 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 a497a629553fa..ce7b158f1908b 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 e8883ad16935b..20cf4d48a08ef 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/control_group/embeddable/control_group_container.tsx b/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx index 06dd2b069bc44..dbcf5e7adc1fb 100644 --- a/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx +++ b/src/plugins/controls/public/control_group/embeddable/control_group_container.tsx @@ -11,6 +11,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { compareFilters, COMPARE_ALL_OPTIONS, Filter, uniqFilters } from '@kbn/es-query'; import { BehaviorSubject, merge, Subject, Subscription } from 'rxjs'; +import _ from 'lodash'; import { EuiContextMenuPanel } from '@elastic/eui'; import { @@ -292,7 +293,10 @@ export class ControlGroupContainer extends Container< } }); // if filters are different, publish them - if (!compareFilters(this.output.filters ?? [], allFilters ?? [], COMPARE_ALL_OPTIONS)) { + if ( + !compareFilters(this.output.filters ?? [], allFilters ?? [], COMPARE_ALL_OPTIONS) || + !_.isEqual(this.output.timeslice, timeslice) + ) { this.updateOutput({ filters: uniqFilters(allFilters), timeslice }); this.onFiltersPublished$.next(allFilters); } diff --git a/src/plugins/controls/public/time_slider/components/index.scss b/src/plugins/controls/public/time_slider/components/index.scss index 1fe17385b6958..20dfd86a23294 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_popover_button.tsx b/src/plugins/controls/public/time_slider/components/time_slider_popover_button.tsx index a0390cc66d156..0036dcdf758e4 100644 --- a/src/plugins/controls/public/time_slider/components/time_slider_popover_button.tsx +++ b/src/plugins/controls/public/time_slider/components/time_slider_popover_button.tsx @@ -18,7 +18,12 @@ interface Props { export function TimeSliderPopoverButton(props: Props) { return ( -
- {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 0790d1d8c4b81..bb22295dfa7e1 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 01b8f0af9538d..a90e9fe563ee0 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 a0006431d9d72..081c062979f7a 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 e151f6a0e0240..dec10fb528422 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 2e95f86e5291d..30ff09f9c492e 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 ac3d9c05c1b97..5b47bb820b9ab 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 20b291120113e..1b89ca7e68c36 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 695412b8bf0b5..30bd9b56a7562 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 b6b1a21f312f9..2a91f9aa5366b 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 1d4bb35558085..a3bf2b7b0cd64 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 0000000000000..424da1b921785 --- /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 5b91d3716ffd9..bf941e0952657 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 1f55f8117c1be..863cd6885fe88 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 7b2ef96ba2b72..4b6f5bf937b07 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 024fbb3577a48..7e016d40cdd39 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 60306ded5d8f4..a035842fa8767 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 fcc9855cf59b6..7c0c4ccb9c498 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 0000000000000..5da647fa1d7e9 --- /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 cb3e465683f81..2baba961691fb 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 9b8b9a7c41039..a99a7a4bfc80b 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 9774d0e4f4fe1..61669d36badad 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 0000000000000..6b724f93bb1a5 --- /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 7aeae229a4f40..5340c6d9b8fd3 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 79f1d2e69856c..a92f0025b8602 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 0000000000000..32f23e41bc4ac --- /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 a8504230a494a..04a3908f5d5b6 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 f828d34bdde12..ce941613e3587 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 ab47d8a575c5b..7886d349044c0 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 0000000000000..5f5cf247f57f8 --- /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 0000000000000..fb01683fff4cb --- /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 0000000000000..c36704a46604d --- /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 0000000000000..d80c1836a9597 --- /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 5ba9ef36197a9..6196d6a162dae 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 a0d86a821afd3..63095f3fbaa2d 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 0000000000000..d204b383055af --- /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 0000000000000..d36a80df6af6a --- /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 0000000000000..969b1af67406d --- /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 c7a6b2d576a30..c39d26314671c 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 4f85fd826cdb8..5a32fdba5e14b 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 2b5ef4cb68df7..fd70516a572d5 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 a81ae20408aa0..0000000000000 --- 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 28c8b8ff10000..960e432762722 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 2327c1c394cee..ac324eac83c37 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 51452dbbd581a..04a11c6c182e6 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 f83dfd3fea11b..c0cb7dd1375f1 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 2805b8389913d..a5c7ff4b67e14 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 4b92c43b2b304..c897ee8007738 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 4529e85d720f8..955687e39f762 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 bd5b561426cfa..a1f7b316b9d48 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 57f82b277467f..b2362b9e300e9 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 0d47c7018d4fe..7ea901f74b1a5 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 521db7971c460..f1385c5223e52 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 193df13c3ad98..822add8f8bde3 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 49f8b76476c51..544d48110941a 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 0000000000000..35610ee803501 --- /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 f2efea5ef1e51..7d328dec9887f 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 cc4a158f41ef6..204435e33f52d 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 0000000000000..34b7f9fdcd7ff --- /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 0000000000000..ad0c2bb45cc39 --- /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 0000000000000..5fb736fa26395 --- /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 0000000000000..5ab99c715453b --- /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 0000000000000..06e37aa2366f8 --- /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 0000000000000..84ceab265e624 --- /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 0000000000000..84867e5bbf893 --- /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 0000000000000..83edd0ed0aaba --- /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 0000000000000..158ab6dcac79d --- /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 0000000000000..f0e2d371dc081 --- /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 0000000000000..981521acf886b --- /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 0000000000000..1b2f8e39e9e58 --- /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 0000000000000..542502fbcbe76 --- /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 0000000000000..e169a76ec8b41 --- /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 0000000000000..d8fcdd3382274 --- /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 0000000000000..a7b2d7b5b63e7 --- /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 d06d9af1dbaf8..6766f0c7535d8 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 25db360a66e71..a4e2fe119eed5 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 0000000000000..a2d21d32fad31 --- /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 4a2ba80ca43ab..8bdbb0a341442 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 0000000000000..99d62926eddd2 --- /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 436e412958178..6634dd84733e3 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 3947e569349c8..89c01bdd8fd7f 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 0af97578b56a4..c3b034f0b6ce7 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 0000000000000..47d36cfff07f5 --- /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 ea3a7f3805d3f..1a609a22da8b8 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 ea19e032c650e..239310731583e 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 0000000000000..7053191ff6afb --- /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 9577eabfd31f3..5874659c690f8 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 a1988570a5873..24994a28c9128 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 2c55ce4a2a08e..e7379ba1e2a4b 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/services/file_storage.ts b/x-pack/plugins/fleet/common/services/file_storage.ts index 4c5e9ac204c58..afabb421e7d45 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/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx index 4189edfbd6184..10d192f9d8fae 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/install_fleet_server.tsx @@ -111,6 +111,7 @@ const InstallFleetServerStepContent: React.FunctionComponent<{ linuxRpmCommand={installCommands.rpm} k8sCommand={installCommands.kubernetes} hasK8sIntegration={false} + hasK8sIntegrationMultiPage={false} hasFleetServer={true} /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx index 61875a063207d..7fbd1c48c363d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx @@ -12,6 +12,7 @@ import { EuiBottomBar, EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } fr import { useLink } from '../../../../../../../hooks'; import { useGetDiscoverLogsLinkForAgents } from '../hooks'; +import { FLEET_KUBERNETES_PACKAGE } from '../../../../../../../../common'; const CenteredRoundedBottomBar = styled(EuiBottomBar)` max-width: 820px; @@ -125,7 +126,9 @@ export const AgentStandaloneBottomBar: React.FC<{ export const CreatePackagePolicyFinalBottomBar: React.FC<{ pkgkey: string; }> = ({ pkgkey }) => { + const isK8s = pkgkey.includes(FLEET_KUBERNETES_PACKAGE); const { getHref } = useLink(); + const { getAbsolutePath } = useLink(); return ( @@ -139,21 +142,40 @@ export const CreatePackagePolicyFinalBottomBar: React.FC<{ - - - - - + {!isK8s && ( + + + + + + )} + {isK8s && ( + + + + + + )} ); 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 ec78655ce7fed..68f78017c962c 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 @@ -11,6 +11,7 @@ import { EuiText, EuiLink, EuiSteps, EuiSpacer } from '@elastic/eui'; import { Error } from '../../../../../../../components'; import { useKibanaVersion, useStartServices } from '../../../../../../../../../hooks'; + import { CreatePackagePolicyBottomBar, NotObscuredByBottomBar } from '../..'; import { InstallManagedAgentStep, @@ -18,6 +19,8 @@ import { } from '../../../../../../../../../components/agent_enrollment_flyout/steps'; import { ManualInstructions } from '../../../../../../../../../components/enrollment_instructions'; +import { KubernetesManifestApplyStep } from '../../../../../../../../../components/agent_enrollment_flyout/steps/run_k8s_apply_command_step'; + import type { InstallAgentPageProps } from './types'; export const InstallElasticAgentManagedPageStep: React.FC = (props) => { @@ -39,6 +42,7 @@ export const InstallElasticAgentManagedPageStep: React.FC const kibanaVersion = useKibanaVersion(); const [commandCopied, setCommandCopied] = useState(false); + const [applyCommandCopied, setApplyCommandCopied] = useState(false); if (!enrollmentAPIKey) { return ( @@ -54,6 +58,9 @@ export const InstallElasticAgentManagedPageStep: React.FC ); } + const isK8s = + props.packageInfo.name === 'kubernetes' ? 'IS_KUBERNETES_MULTIPAGE' : 'IS_NOT_KUBERNETES'; + const installManagedCommands = ManualInstructions( enrollmentAPIKey.api_key, fleetServerHosts, @@ -65,19 +72,33 @@ export const InstallElasticAgentManagedPageStep: React.FC installCommand: installManagedCommands, apiKeyData: { item: enrollmentAPIKey }, enrollToken: enrollmentAPIKey.api_key, + isK8s, selectedApiKeyId: enrollmentAPIKey.id, isComplete: commandCopied || !!enrolledAgentIds.length, fullCopyButton: true, onCopy: () => setCommandCopied(true), }), + ]; + + if (isK8s === 'IS_KUBERNETES_MULTIPAGE') { + steps.push( + KubernetesManifestApplyStep({ + isComplete: applyCommandCopied || !!enrolledAgentIds.length, + fullCopyButton: true, + onCopy: () => setApplyCommandCopied(true), + }) + ); + } + + steps.push( AgentEnrollmentConfirmationStep({ selectedPolicyId: agentPolicy?.id, troubleshootLink: link, agentCount: enrolledAgentIds.length, showLoading: true, poll: commandCopied, - }), - ]; + }) + ); return ( <> 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 b52a0401b8650..76bcd21c0207c 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 ad9c9bdbf3f7b..6fdf4016d457f 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/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts index f4ac18057dbbf..572a0d33d3f04 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts @@ -14,7 +14,6 @@ const EXCLUDED_PACKAGES = [ 'cloud_security_posture', 'dga', 'fleet_server', - 'kubernetes', 'osquery_manager', 'problemchild', 'security_detection_engine', diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/installation_message.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/installation_message.tsx index f730423b0f7ae..7462c0af0c09f 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/installation_message.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/installation_message.tsx @@ -35,42 +35,52 @@ export const InstallationMessage: React.FunctionComponent = ({ return ( <> - - - - - ), - installationLink: ( - - - - ), - }} - /> - + {isK8s !== 'IS_KUBERNETES_MULTIPAGE' && ( + + + + + ), + installationLink: ( + + + + ), + }} + /> + + )} + {isK8s === 'IS_KUBERNETES_MULTIPAGE' && ( + + + + )} ); diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.tsx index 1c77e1675a986..e992a58c1c100 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/kubernetes_instructions.tsx @@ -26,15 +26,35 @@ import { sendGetK8sManifest } from '../../hooks/use_request/k8s'; interface Props { enrollmentAPIKey?: string; + onCopy?: () => void; + onDownload?: () => void; } -export const KubernetesInstructions: React.FunctionComponent = ({ enrollmentAPIKey }) => { +export const KubernetesInstructions: React.FunctionComponent = ({ + enrollmentAPIKey, + onCopy, + onDownload, +}) => { const core = useStartServices(); const settings = useGetSettings(); const { notifications } = core; const [yaml, setYaml] = useState(''); const [fleetServer, setFleetServer] = useState(); + const [copyButtonClicked, setCopyButtonClicked] = useState(false); + const [downloadButtonClicked, setDownloadButtonClicked] = useState(false); + + const onCopyButtonClick = (copy: () => void) => { + copy(); + setCopyButtonClicked(true); + if (onCopy) onCopy(); + }; + + const onDownloadButtonClick = (downloadLink: string) => { + setDownloadButtonClicked(true); + if (onDownload) onDownload(); + window.location.href = downloadLink; + }; useEffect(() => { async function fetchK8sManifest() { @@ -77,49 +97,70 @@ export const KubernetesInstructions: React.FunctionComponent = ({ enrollm const k8sCopyYaml = ( {(copy) => ( - - + onCopyButtonClick(copy)} iconType="copyClipboard"> + {copyButtonClicked ? ( + + ) : ( + + )} )} ); - const k8sYaml = ( - - {yaml} - - ); - const downloadLink = core.http.basePath.prepend( `${agentPolicyRouteService.getK8sFullDownloadPath()}?fleetServer=${fleetServer}&enrolToken=${enrollmentAPIKey}` ); - const downloadMsg = ( - + const k8sDownloadYaml = ( + <> + onDownloadButtonClick(downloadLink)} + > + {downloadButtonClicked ? ( + + ) : ( + + )} + + + ); + + const k8sYaml = ( + + {yaml} + ); return ( <> {downloadDescription} + <>{k8sYaml} + <>{k8sCopyYaml} - - <>{downloadMsg} - + <>{k8sDownloadYaml} - - <>{k8sYaml} ); }; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/run_k8s_apply_command_step.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/run_k8s_apply_command_step.tsx new file mode 100644 index 0000000000000..539bcfb660637 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/run_k8s_apply_command_step.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; + +import { i18n } from '@kbn/i18n'; + +import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButton, EuiCodeBlock, EuiCopy, EuiSpacer, EuiText } from '@elastic/eui'; +import styled from 'styled-components'; + +export const KubernetesManifestApplyStep = ({ + isComplete, + fullCopyButton, + onCopy, +}: { + isComplete?: boolean; + fullCopyButton?: boolean; + onCopy?: () => void; +}): EuiContainedStepProps => { + const [copyButtonClicked, setCopyButtonClicked] = useState(false); + // Otherwise the copy button is over the text + const CommandCode = styled.pre({ + overflow: 'auto', + }); + const onTextAreaClick = () => { + if (onCopy) onCopy(); + }; + const onCopyButtonClick = (copy: () => void) => { + copy(); + setCopyButtonClicked(true); + if (onCopy) onCopy(); + }; + const status = isComplete ? 'complete' : undefined; + return { + status, + title: i18n.translate('xpack.fleet.agentEnrollment.stepKubernetesApplyManifest', { + defaultMessage: 'Run the apply command', + }), + children: ( + <> + + + + + + + kubectl apply -f elastic-agent-managed-kubernetes.yaml + + + {fullCopyButton && ( + + {(copy) => ( + onCopyButtonClick(copy)} + > + {copyButtonClicked ? ( + + ) : ( + + )} + + )} + + )} + + ), + }; +}; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts index 0cbae55c6ffd9..fdf4404b2f43b 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts @@ -9,7 +9,11 @@ import type { AgentPolicy } from '../../types'; import type { InstalledIntegrationPolicy } from './use_get_agent_incoming_data'; -export type K8sMode = 'IS_LOADING' | 'IS_KUBERNETES' | 'IS_NOT_KUBERNETES'; +export type K8sMode = + | 'IS_LOADING' + | 'IS_KUBERNETES' + | 'IS_NOT_KUBERNETES' + | 'IS_KUBERNETES_MULTIPAGE'; export type FlyoutMode = 'managed' | 'standalone'; export type SelectionType = 'tabs' | 'radio' | undefined; diff --git a/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx b/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx index f363584399d55..07877eec1e80e 100644 --- a/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx +++ b/x-pack/plugins/fleet/public/components/enrollment_instructions/install_section.tsx @@ -43,7 +43,8 @@ export const InstallSection: React.FunctionComponent = ({ linuxDebCommand={installCommand.deb} linuxRpmCommand={installCommand.rpm} k8sCommand={installCommand.kubernetes} - hasK8sIntegration={isK8s === 'IS_KUBERNETES'} + hasK8sIntegration={isK8s === 'IS_KUBERNETES' || isK8s === 'IS_KUBERNETES_MULTIPAGE'} + hasK8sIntegrationMultiPage={isK8s === 'IS_KUBERNETES_MULTIPAGE'} isManaged={isManaged} enrollToken={enrollToken} /> diff --git a/x-pack/plugins/fleet/public/components/platform_selector.tsx b/x-pack/plugins/fleet/public/components/platform_selector.tsx index 89027b217c5da..418b788360060 100644 --- a/x-pack/plugins/fleet/public/components/platform_selector.tsx +++ b/x-pack/plugins/fleet/public/components/platform_selector.tsx @@ -32,6 +32,7 @@ interface Props { linuxRpmCommand: string; k8sCommand: string; hasK8sIntegration: boolean; + hasK8sIntegrationMultiPage: boolean; isManaged?: boolean; hasFleetServer?: boolean; enrollToken?: string | undefined; @@ -52,6 +53,7 @@ export const PlatformSelector: React.FunctionComponent = ({ linuxRpmCommand, k8sCommand, hasK8sIntegration, + hasK8sIntegrationMultiPage, isManaged, enrollToken, hasFleetServer, @@ -112,14 +114,16 @@ export const PlatformSelector: React.FunctionComponent = ({ return ( <> <> - setPlatform(id as PLATFORM_TYPE)} - legend={i18n.translate('xpack.fleet.enrollmentInstructions.platformSelectAriaLabel', { - defaultMessage: 'Platform', - })} - /> + {!hasK8sIntegrationMultiPage && ( + setPlatform(id as PLATFORM_TYPE)} + legend={i18n.translate('xpack.fleet.enrollmentInstructions.platformSelectAriaLabel', { + defaultMessage: 'Platform', + })} + /> + )} {(platform === 'deb' || platform === 'rpm') && ( <> @@ -135,55 +139,63 @@ export const PlatformSelector: React.FunctionComponent = ({ )} {platform === 'kubernetes' && isManaged && ( <> - + )} - {platform === 'kubernetes' && ( - + {!hasK8sIntegrationMultiPage && ( + <> + {platform === 'kubernetes' && ( + + + + + + )} + + {commandsByPlatform[platform]} + - - - - )} - - {commandsByPlatform[platform]} - - - {fullCopyButton && ( - - {(copy) => ( - onCopyButtonClick(copy)} - > - {copyButtonClicked ? ( - - ) : ( - + {fullCopyButton && ( + + {(copy) => ( + onCopyButtonClick(copy)} + > + {copyButtonClicked ? ( + + ) : ( + + )} + )} - + )} - + )} 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 f4cd7f9403280..9a6f48132e0a6 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/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 14a773cfd94bf..ca03d272405f4 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 78683ecd07e0a..4b0aed34f8520 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 3c4861b563b08..82c3175583a75 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/setup.test.ts b/x-pack/plugins/fleet/server/services/setup.test.ts index 34336f8167316..8837ae3522ac1 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 3cb2dc030cf75..e43efb44adb5f 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/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 bbd0afa495e48..4a251d41d69d9 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 1f360a671aaae..0148feff83d58 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 0000000000000..de21a883e9ab3 --- /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 0000000000000..f2d0723590cf5 --- /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 ecdaa70d7869b..562ebffc9f00c 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 35a28c0116035..6b87945710a37 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 f4aa35675cfd1..36cdedee82c40 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 973b2bb7335a3..98e2ecb0ede5d 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 8104d9ef3d51d..c7191118e2599 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 0000000000000..3ee53940add2b --- /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 9cfa17fe71941..c7abf7385c3b0 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 fa47ae09822e2..48cb2e9f8cd36 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 3f70e0c58324d..a78a1e3815be8 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 1ef553eae92f7..12bd498c98fa9 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 6959f5cf17a53..2d755d3cb1d54 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/components/scatterplot_matrix/scatterplot_matrix.tsx b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx index ee9cf024a6749..e822d2ebd91d7 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx @@ -29,7 +29,6 @@ import { stringHash } from '@kbn/ml-string-hash'; import { extractErrorMessage } from '../../../../common'; import { isRuntimeMappings } from '../../../../common/util/runtime_field_utils'; import { RuntimeMappings } from '../../../../common/types/fields'; -import type { ResultsSearchQuery } from '../../data_frame_analytics/common/analytics'; import { getCombinedRuntimeMappings } from '../data_grid'; import { useMlApiContext } from '../../contexts/kibana'; @@ -81,13 +80,25 @@ const OptionLabelWithIconTip: FC = ({ label, toolti ); +function filterChartableItems(items: estypes.SearchHit[], resultsField?: string) { + return ( + items + .map((d) => + getProcessedFields(d.fields ?? {}, (key: string) => + key.startsWith(`${resultsField}.feature_importance`) + ) + ) + .filter((d) => !Object.keys(d).some((field) => Array.isArray(d[field]))) ?? [] + ); +} + export interface ScatterplotMatrixProps { fields: string[]; index: string; resultsField?: string; color?: string; legendType?: LegendType; - searchQuery?: ResultsSearchQuery; + searchQuery?: estypes.QueryDslQueryContainer; runtimeMappings?: RuntimeMappings; indexPattern?: DataView; } @@ -128,7 +139,7 @@ export const ScatterplotMatrix: FC = ({ // contains the fetched documents and columns to be passed on to the Vega spec. const [splom, setSplom] = useState< - { items: any[]; columns: string[]; messages: string[] } | undefined + { items: any[]; backgroundItems: any[]; columns: string[]; messages: string[] } | undefined >(); // formats the array of field names for EuiComboBox @@ -165,7 +176,7 @@ export const ScatterplotMatrix: FC = ({ useEffect(() => { if (fields.length === 0) { - setSplom({ columns: [], items: [], messages: [] }); + setSplom({ columns: [], items: [], backgroundItems: [], messages: [] }); setIsLoading(false); return; } @@ -184,7 +195,7 @@ export const ScatterplotMatrix: FC = ({ ...(includeOutlierScoreField ? [outlierScoreField] : []), ]; - const query = randomizeQuery + const foregroundQuery = randomizeQuery ? { function_score: { query: searchQuery, @@ -193,33 +204,65 @@ export const ScatterplotMatrix: FC = ({ } : searchQuery; + let backgroundQuery; + // If it's not the default query then we do a background search excluding the current query + if ( + searchQuery && + ((searchQuery.match_all && Object.keys(searchQuery.match_all).length > 0) || + (searchQuery.bool && Object.keys(searchQuery.bool).length > 0)) + ) { + backgroundQuery = randomizeQuery + ? { + function_score: { + query: { bool: { must_not: [searchQuery] } }, + random_score: { seed: 10, field: '_seq_no' }, + }, + } + : { bool: { must_not: [searchQuery] } }; + } + const combinedRuntimeMappings = indexPattern && getCombinedRuntimeMappings(indexPattern, runtimeMappings); - const resp: estypes.SearchResponse = await esSearch({ - index, - body: { - fields: queryFields, - _source: false, - query, - from: 0, - size: fetchSize, - ...(isRuntimeMappings(combinedRuntimeMappings) - ? { runtime_mappings: combinedRuntimeMappings } - : {}), - }, - }); + const body = { + fields: queryFields, + _source: false, + query: foregroundQuery, + from: 0, + size: fetchSize, + ...(isRuntimeMappings(combinedRuntimeMappings) + ? { runtime_mappings: combinedRuntimeMappings } + : {}), + }; + + const promises = [ + esSearch({ + index, + body, + }), + ]; + + if (backgroundQuery) { + promises.push( + esSearch({ + index, + body: { ...body, query: backgroundQuery }, + }) + ); + } + + const [foregroundResp, backgroundResp] = await Promise.all( + promises + ); if (!options.didCancel) { - const items = resp.hits.hits - .map((d) => - getProcessedFields(d.fields ?? {}, (key: string) => - key.startsWith(`${resultsField}.feature_importance`) - ) - ) - .filter((d) => !Object.keys(d).some((field) => Array.isArray(d[field]))); - - const originalDocsCount = resp.hits.hits.length; + const items = filterChartableItems(foregroundResp.hits.hits, resultsField); + const backgroundItems = filterChartableItems( + backgroundResp?.hits.hits ?? [], + resultsField + ); + + const originalDocsCount = foregroundResp.hits.hits.length; const filteredDocsCount = originalDocsCount - items.length; if (originalDocsCount === filteredDocsCount) { @@ -229,7 +272,7 @@ export const ScatterplotMatrix: FC = ({ 'All fetched documents included fields with arrays of values and cannot be visualized.', }) ); - } else if (resp.hits.hits.length !== items.length) { + } else if (foregroundResp.hits.hits.length !== items.length) { messages.push( i18n.translate('xpack.ml.splom.arrayFieldsWarningMessage', { defaultMessage: @@ -242,12 +285,17 @@ export const ScatterplotMatrix: FC = ({ ); } - setSplom({ columns: fields, items, messages }); + setSplom({ columns: fields, items, backgroundItems, messages }); setIsLoading(false); } } catch (e) { setIsLoading(false); - setSplom({ columns: [], items: [], messages: [extractErrorMessage(e)] }); + setSplom({ + columns: [], + items: [], + backgroundItems: [], + messages: [extractErrorMessage(e)], + }); } } @@ -265,10 +313,11 @@ export const ScatterplotMatrix: FC = ({ return; } - const { items, columns } = splom; + const { items, backgroundItems, columns } = splom; return getScatterplotMatrixVegaLiteSpec( items, + backgroundItems, columns, euiTheme, resultsField, @@ -409,7 +458,25 @@ export const ScatterplotMatrix: FC = ({ )} - {splom.items.length > 0 && } + {splom.items.length > 0 && ( + <> + + {splom.backgroundItems.length ? ( + <> + + + <> + + + ) : null} + + )}
)} diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts index 89ee0add9966e..0fbe08dd24af7 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts @@ -73,7 +73,8 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { it('should return the default spec for non-outliers without a legend', () => { const data = [{ x: 1, y: 1 }]; - const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec(data, ['x', 'y'], euiThemeLight); + const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec(data, [], ['x', 'y'], euiThemeLight); + const specForegroundLayer = vegaLiteSpec.spec.layer[0]; // A valid Vega Lite spec shouldn't throw an error when compiled. expect(() => compile(vegaLiteSpec)).not.toThrow(); @@ -82,17 +83,17 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { column: ['x', 'y'], row: ['y', 'x'], }); - expect(vegaLiteSpec.spec.data.values).toEqual(data); - expect(vegaLiteSpec.spec.mark).toEqual({ + expect(specForegroundLayer.data.values).toEqual(data); + expect(specForegroundLayer.mark).toEqual({ opacity: 0.75, size: 8, type: 'circle', }); - expect(vegaLiteSpec.spec.encoding.color).toEqual({ + expect(specForegroundLayer.encoding.color).toEqual({ condition: [{ selection: USER_SELECTION }, { selection: SINGLE_POINT_CLICK }], value: COLOR_BLUR, }); - expect(vegaLiteSpec.spec.encoding.tooltip).toEqual([ + expect(specForegroundLayer.encoding.tooltip).toEqual([ { field: 'x', type: 'quantitative' }, { field: 'y', type: 'quantitative' }, ]); @@ -101,7 +102,14 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { it('should return the spec for outliers', () => { const data = [{ x: 1, y: 1 }]; - const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec(data, ['x', 'y'], euiThemeLight, 'ml'); + const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec( + data, + [], + ['x', 'y'], + euiThemeLight, + 'ml' + ); + const specForegroundLayer = vegaLiteSpec.spec.layer[0]; // A valid Vega Lite spec shouldn't throw an error when compiled. expect(() => compile(vegaLiteSpec)).not.toThrow(); @@ -110,13 +118,13 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { column: ['x', 'y'], row: ['y', 'x'], }); - expect(vegaLiteSpec.spec.data.values).toEqual(data); - expect(vegaLiteSpec.spec.mark).toEqual({ + expect(specForegroundLayer.data.values).toEqual(data); + expect(specForegroundLayer.mark).toEqual({ opacity: 0.75, size: 8, type: 'circle', }); - expect(vegaLiteSpec.spec.encoding.color).toEqual({ + expect(specForegroundLayer.encoding.color).toEqual({ condition: { selection: USER_SELECTION, field: 'is_outlier', @@ -127,7 +135,7 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { }, value: COLOR_BLUR, }); - expect(vegaLiteSpec.spec.encoding.tooltip).toEqual([ + expect(specForegroundLayer.encoding.tooltip).toEqual([ { field: 'x', type: 'quantitative' }, { field: 'y', type: 'quantitative' }, { @@ -144,12 +152,14 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec( data, + [], ['x', 'y'], euiThemeLight, undefined, 'the-color-field', LEGEND_TYPES.NOMINAL ); + const specForegroundLayer = vegaLiteSpec.spec.layer[0]; // A valid Vega Lite spec shouldn't throw an error when compiled. expect(() => compile(vegaLiteSpec)).not.toThrow(); @@ -158,13 +168,13 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { column: ['x', 'y'], row: ['y', 'x'], }); - expect(vegaLiteSpec.spec.data.values).toEqual(data); - expect(vegaLiteSpec.spec.mark).toEqual({ + expect(specForegroundLayer.data.values).toEqual(data); + expect(specForegroundLayer.mark).toEqual({ opacity: 0.75, size: 8, type: 'circle', }); - expect(vegaLiteSpec.spec.encoding.color).toEqual({ + expect(specForegroundLayer.encoding.color).toEqual({ condition: { selection: USER_SELECTION, field: 'the-color-field', @@ -175,7 +185,7 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { }, value: COLOR_BLUR, }); - expect(vegaLiteSpec.spec.encoding.tooltip).toEqual([ + expect(specForegroundLayer.encoding.tooltip).toEqual([ { field: 'the-color-field', type: 'nominal' }, { field: 'x', type: 'quantitative' }, { field: 'y', type: 'quantitative' }, @@ -187,12 +197,14 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec( data, + [], ['x.a', 'y[a]'], euiThemeLight, undefined, 'the-color-field', LEGEND_TYPES.NOMINAL ); + const specForegroundLayer = vegaLiteSpec.spec.layer[0]; // column values should be escaped expect(vegaLiteSpec.repeat).toEqual({ @@ -200,6 +212,6 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { row: ['y\\[a\\]', 'x\\.a'], }); // raw data should not be escaped - expect(vegaLiteSpec.spec.data.values).toEqual(data); + expect(specForegroundLayer.data.values).toEqual(data); }); }); diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts index 50b9fbf76daf2..de29332f57ef6 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts @@ -75,6 +75,151 @@ export const getColorSpec = ( }; }; +const getVegaSpecLayer = ( + isBackground: boolean, + values: VegaValue[], + colorSpec: any, + escapedOutlierScoreField: string, + outliers: boolean, + dynamicSize: boolean, + vegaColumns: string[], + color?: string +) => { + const selection = outliers + ? { + selection: { + [USER_SELECTION]: { type: 'interval' }, + [SINGLE_POINT_CLICK]: { type: 'single' }, + mlOutlierScoreThreshold: { + type: 'single', + fields: ['cutoff'], + bind: { + input: 'range', + max: 1, + min: 0, + name: i18n.translate('xpack.ml.splomSpec.outlierScoreThresholdName', { + defaultMessage: 'Outlier score threshold: ', + }), + step: 0.01, + }, + init: { cutoff: 0.99 }, + }, + }, + } + : { + selection: { + // Always allow user selection + [USER_SELECTION]: { + type: 'interval', + }, + [SINGLE_POINT_CLICK]: { type: 'single', empty: 'none' }, + }, + }; + + return { + data: { values: [...values] }, + mark: { + ...(outliers && dynamicSize + ? { + type: 'circle', + strokeWidth: 1.2, + strokeOpacity: 0.75, + fillOpacity: 0.1, + } + : { type: 'circle', opacity: 0.75, size: 8 }), + }, + // transformation to apply outlier threshold as category + ...(outliers + ? { + transform: [ + { + calculate: `datum['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff`, + as: 'is_outlier', + }, + ], + } + : {}), + encoding: { + color: isBackground ? { value: COLOR_BLUR } : colorSpec, + opacity: { + condition: { + selection: USER_SELECTION, + value: 0.8, + }, + value: 0.5, + }, + ...(dynamicSize + ? { + stroke: colorSpec, + opacity: { + condition: { + value: 1, + test: `(datum['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff)`, + }, + value: 0.5, + }, + } + : {}), + ...(outliers + ? { + order: { field: escapedOutlierScoreField }, + size: { + ...(!dynamicSize + ? { + condition: { + value: 40, + test: `(datum['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff)`, + }, + value: 8, + } + : { + type: LEGEND_TYPES.QUANTITATIVE, + field: escapedOutlierScoreField, + scale: { + type: 'linear', + range: [8, 200], + domain: [0, 1], + }, + }), + }, + } + : {}), + x: { + type: LEGEND_TYPES.QUANTITATIVE, + field: { repeat: 'column' }, + scale: { zero: false }, + }, + y: { + type: LEGEND_TYPES.QUANTITATIVE, + field: { repeat: 'row' }, + scale: { zero: false }, + }, + tooltip: [ + ...(color !== undefined + ? // @ts-ignore + [{ type: colorSpec.condition.type, field: getEscapedVegaFieldName(color) }] + : []), + ...vegaColumns.map((d) => ({ + type: LEGEND_TYPES.QUANTITATIVE, + field: d, + })), + ...(outliers + ? [ + { + type: LEGEND_TYPES.QUANTITATIVE, + field: escapedOutlierScoreField, + format: '.3f', + }, + ] + : []), + ], + }, + ...(isBackground ? {} : selection), + width: SCATTERPLOT_SIZE, + height: SCATTERPLOT_SIZE, + }; +}; + // Escapes the characters .[] in field names with double backslashes // since VEGA treats dots/brackets in field names as nested values. // See https://vega.github.io/vega-lite/docs/field.html for details. @@ -86,6 +231,7 @@ type VegaValue = Record; export const getScatterplotMatrixVegaLiteSpec = ( values: VegaValue[], + backgroundValues: VegaValue[], columns: string[], euiTheme: typeof euiThemeLight, resultsField?: string, @@ -106,7 +252,7 @@ export const getScatterplotMatrixVegaLiteSpec = ( legendType ); - return { + const schema: TopLevelSpec = { $schema: 'https://vega.github.io/schema/vega-lite/v4.17.0.json', background: 'transparent', // There seems to be a bug in Vega which doesn't propagate these settings @@ -134,129 +280,35 @@ export const getScatterplotMatrixVegaLiteSpec = ( row: vegaColumns.slice().reverse(), }, spec: { - data: { values: [...vegaValues] }, - mark: { - ...(outliers && dynamicSize - ? { - type: 'circle', - strokeWidth: 1.2, - strokeOpacity: 0.75, - fillOpacity: 0.1, - } - : { type: 'circle', opacity: 0.75, size: 8 }), - }, - // transformation to apply outlier threshold as category - ...(outliers - ? { - transform: [ - { - calculate: `datum['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff`, - as: 'is_outlier', - }, - ], - } - : {}), - encoding: { - color: colorSpec, - opacity: { - condition: { - selection: USER_SELECTION, - value: 0.8, - }, - value: 0.5, - }, - ...(dynamicSize - ? { - stroke: colorSpec, - opacity: { - condition: { - value: 1, - test: `(datum['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff)`, - }, - value: 0.5, - }, - } - : {}), - ...(outliers - ? { - order: { field: escapedOutlierScoreField }, - size: { - ...(!dynamicSize - ? { - condition: { - value: 40, - test: `(datum['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff)`, - }, - value: 8, - } - : { - type: LEGEND_TYPES.QUANTITATIVE, - field: escapedOutlierScoreField, - scale: { - type: 'linear', - range: [8, 200], - domain: [0, 1], - }, - }), - }, - } - : {}), - x: { - type: LEGEND_TYPES.QUANTITATIVE, - field: { repeat: 'column' }, - scale: { zero: false }, - }, - y: { - type: LEGEND_TYPES.QUANTITATIVE, - field: { repeat: 'row' }, - scale: { zero: false }, - }, - tooltip: [ - ...(color !== undefined - ? // @ts-ignore - [{ type: colorSpec.condition.type, field: getEscapedVegaFieldName(color) }] - : []), - ...vegaColumns.map((d) => ({ - type: LEGEND_TYPES.QUANTITATIVE, - field: d, - })), - ...(outliers - ? [{ type: LEGEND_TYPES.QUANTITATIVE, field: escapedOutlierScoreField, format: '.3f' }] - : []), - ], - }, - ...(outliers - ? { - selection: { - [USER_SELECTION]: { type: 'interval' }, - [SINGLE_POINT_CLICK]: { type: 'single' }, - mlOutlierScoreThreshold: { - type: 'single', - fields: ['cutoff'], - bind: { - input: 'range', - max: 1, - min: 0, - name: i18n.translate('xpack.ml.splomSpec.outlierScoreThresholdName', { - defaultMessage: 'Outlier score threshold: ', - }), - step: 0.01, - }, - init: { cutoff: 0.99 }, - }, - }, - } - : { - selection: { - // Always allow user selection - [USER_SELECTION]: { - type: 'interval', - }, - [SINGLE_POINT_CLICK]: { type: 'single', empty: 'none' }, - }, - }), - width: SCATTERPLOT_SIZE, - height: SCATTERPLOT_SIZE, + layer: [ + getVegaSpecLayer( + false, + vegaValues, + colorSpec, + escapedOutlierScoreField, + outliers, + !!dynamicSize, + vegaColumns, + color + ), + ], }, }; + + if (backgroundValues.length) { + schema.spec.layer.unshift( + getVegaSpecLayer( + true, + backgroundValues, + colorSpec, + escapedOutlierScoreField, + outliers, + !!dynamicSize, + vegaColumns, + color + ) + ); + } + + return schema; }; 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 5b9e2f7d2ab27..d88d9abb24a87 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/jobs/jobs_list/components/datafeed_chart_flyout/datafeed_chart_flyout.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/datafeed_chart_flyout.tsx index ebf522a4d6bdf..77f139931986c 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/datafeed_chart_flyout.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/datafeed_chart_flyout.tsx @@ -44,6 +44,8 @@ import { timeFormatter, RectAnnotationEvent, LineAnnotationEvent, + Tooltip, + TooltipType, } from '@elastic/charts'; import { DATAFEED_STATE } from '../../../../../../common/constants/states'; @@ -63,6 +65,7 @@ import { EditQueryDelay } from './edit_query_delay'; import { CHART_DIRECTION, ChartDirectionType, CHART_SIZE } from './constants'; import { loadFullJob } from '../utils'; import { checkPermission } from '../../../../capabilities/check_capabilities'; +import { fillMissingChartData, type ChartDataWithNullValues } from './fill_missing_chart_data'; const dateFormatter = timeFormatter('MM-DD HH:mm:ss'); const MAX_CHART_POINTS = 480; @@ -72,6 +75,9 @@ const revertSnapshotMessage = i18n.translate( defaultMessage: 'Click to revert to this model snapshot.', } ); +const notAvailableMessage = i18n.translate('xpack.ml.jobsList.datafeedChart.notAvailableMessage', { + defaultMessage: 'N/A', +}); interface DatafeedChartFlyoutProps { jobId: string; @@ -114,7 +120,7 @@ export const DatafeedChartFlyout: FC = ({ }); const [endDate, setEndDate] = useState(moment(end)); const [isLoadingChartData, setIsLoadingChartData] = useState(false); - const [bucketData, setBucketData] = useState([]); + const [bucketData, setBucketData] = useState([]); const [annotationData, setAnnotationData] = useState<{ rect: RectAnnotationDatum[]; line: LineAnnotationDatum[]; @@ -123,7 +129,7 @@ export const DatafeedChartFlyout: FC = ({ LineAnnotationDatumWithModelSnapshot[] >([]); const [messageData, setMessageData] = useState([]); - const [sourceData, setSourceData] = useState([]); + const [sourceData, setSourceData] = useState([]); const [showAnnotations, setShowAnnotations] = useState(true); const [showModelSnapshots, setShowModelSnapshots] = useState(true); const [range, setRange] = useState<{ start: string; end: string } | undefined>(); @@ -170,9 +176,21 @@ export const DatafeedChartFlyout: FC = ({ try { const chartData = await getDatafeedResultChartData(jobId, startTimestamp, endTimestamp); + let chartSourceData: ChartDataWithNullValues = + chartData.datafeedResults as ChartDataWithNullValues; + let chartBucketData: ChartDataWithNullValues = + chartData.bucketResults as ChartDataWithNullValues; + + if (chartSourceData.length !== chartBucketData.length) { + if (chartSourceData.length > chartBucketData.length) { + chartBucketData = fillMissingChartData(chartBucketData, chartSourceData); + } else { + chartSourceData = fillMissingChartData(chartSourceData, chartBucketData); + } + } - setSourceData(chartData.datafeedResults); - setBucketData(chartData.bucketResults); + setSourceData(chartSourceData); + setBucketData(chartBucketData); setAnnotationData({ rect: chartData.annotationResultsRect, line: chartData.annotationResultsLine.map(setLineAnnotationHeader), @@ -378,6 +396,7 @@ export const DatafeedChartFlyout: FC = ({
+ = ({ defaultMessage: 'Count', })} position={Position.Left} + tickFormat={(d) => (d === null ? notAvailableMessage : d)} /> {showAnnotations ? ( <> diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/fill_missing_chart_data.test.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/fill_missing_chart_data.test.ts new file mode 100644 index 0000000000000..5ef38a4a0e51e --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/fill_missing_chart_data.test.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 { fillMissingChartData, ChartDataWithNullValues } from './fill_missing_chart_data'; + +const completeData: ChartDataWithNullValues = [ + [1666828800000, 7], + [1666832400000, 6], + [1666836000000, 6], + [1666839600000, 5], + [1666843200000, 4], + [1666846800000, 6], +]; + +describe('buildBaseFilterCriteria', () => { + it('returns chart data with missing timestamps in middle of dataset filled in to null', () => { + const dataWithMissingValues: ChartDataWithNullValues = [ + [1666828800000, 7], + [1666832400000, 6], + [1666839600000, 5], + [1666843200000, 4], + [1666846800000, 6], + ]; + const expectedData: ChartDataWithNullValues = [ + [1666828800000, 7], + [1666832400000, 6], + [1666836000000, null], + [1666839600000, 5], + [1666843200000, 4], + [1666846800000, 6], + ]; + const dataWithFilledValues = fillMissingChartData(dataWithMissingValues, completeData); + + expect(dataWithFilledValues).toEqual(expectedData); + }); + + it('returns chart data with missing timestamps at start of dataset filled in to null', () => { + const dataWithMissingValues: ChartDataWithNullValues = [ + [1666832400000, 6], + [1666836000000, 6], + [1666839600000, 5], + [1666843200000, 4], + [1666846800000, 6], + ]; + const expectedData: ChartDataWithNullValues = [ + [1666828800000, null], + [1666832400000, 6], + [1666836000000, 6], + [1666839600000, 5], + [1666843200000, 4], + [1666846800000, 6], + ]; + const dataWithFilledValues = fillMissingChartData(dataWithMissingValues, completeData); + + expect(dataWithFilledValues).toEqual(expectedData); + }); + + it('returns chart data with missing timestamps at end of dataset filled in to null', () => { + const dataWithMissingValues: ChartDataWithNullValues = [ + [1666828800000, 7], + [1666832400000, 6], + [1666836000000, 6], + [1666839600000, 5], + ]; + const expectedData: ChartDataWithNullValues = [ + [1666828800000, 7], + [1666832400000, 6], + [1666836000000, 6], + [1666839600000, 5], + [1666843200000, null], + [1666846800000, null], + ]; + const dataWithFilledValues = fillMissingChartData(dataWithMissingValues, completeData); + + expect(dataWithFilledValues).toEqual(expectedData); + }); +}); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/fill_missing_chart_data.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/fill_missing_chart_data.ts new file mode 100644 index 0000000000000..68f5e973019e3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/datafeed_chart_flyout/fill_missing_chart_data.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type ChartDataWithNullValues = Array<[number, number | null]>; + +/** + * Returns an array with the filled in missing timestamp values for scatterplot charts. + * @param dataWithPossibleMissingValues - An array of arrays consisting of [timestamp, doc_count] + * @param dataWithAllValues - An array of arrays consisting of [timestamp, doc_count] + */ +export const fillMissingChartData = ( + dataWithPossibleMissingValues: ChartDataWithNullValues, + dataWithAllValues: ChartDataWithNullValues +): ChartDataWithNullValues => { + const mappedData = new Map(dataWithPossibleMissingValues); + + const filledData = dataWithAllValues.reduce((acc, source) => { + acc.push( + mappedData.has(source[0]) ? [source[0], mappedData.get(source[0]) ?? null] : [source[0], null] + ); + return acc; + }, []); + + return filledData; +}; diff --git a/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts b/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts index 9ae337cfcd7f0..dc21715d963a8 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 0000000000000..47be592377825 --- /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 5b55d41e887bc..92c96f4844326 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 9323d86d32b99..b7513db067e2f 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/application/services/ml_api_service/trained_models.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts index 4bad39b54cf3e..2d8864d755f3b 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts @@ -157,7 +157,11 @@ export function trainedModelsApiProvider(httpService: HttpService) { }); }, - inferTrainedModel(modelId: string, payload: any, timeout?: string) { + inferTrainedModel( + modelId: string, + payload: estypes.MlInferTrainedModelRequest['body'], + timeout?: string + ) { const body = JSON.stringify(payload); return httpService.http({ path: `${apiBasePath}/trained_models/infer/${modelId}`, @@ -166,6 +170,21 @@ export function trainedModelsApiProvider(httpService: HttpService) { ...(timeout ? { query: { timeout } as HttpFetchQuery } : {}), }); }, + + trainedModelPipelineSimulate( + pipeline: estypes.IngestPipeline, + docs: estypes.IngestSimulateDocument[] + ) { + const body = JSON.stringify({ + pipeline, + docs, + }); + return httpService.http({ + path: `${apiBasePath}/trained_models/pipeline_simulate`, + method: 'POST', + body, + }); + }, }; } diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/model_actions.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/model_actions.tsx index 2bd18a782f071..81c87dd05c53b 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/model_actions.tsx @@ -31,7 +31,7 @@ export function useModelActions({ fetchModels, }: { isLoading: boolean; - onTestAction: (model: ModelItem) => void; + onTestAction: (model: string) => void; onModelsDeleteRequest: (modelsIds: string[]) => void; onLoading: (isLoading: boolean) => void; fetchModels: () => void; @@ -360,7 +360,7 @@ export function useModelActions({ type: 'icon', isPrimary: true, available: isTestable, - onClick: onTestAction, + onClick: (item) => onTestAction(item.model_id), enabled: (item) => canTestTrainedModels && isTestEnabled(item), }, ], diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx index b14a12b1b904f..b16fd175c58c6 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx @@ -115,7 +115,7 @@ export const ModelsList: FC = ({ const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>( {} ); - const [showTestFlyout, setShowTestFlyout] = useState(null); + const [showTestFlyout, setShowTestFlyout] = useState(null); const isBuiltInModel = useCallback( (item: ModelItem) => item.tags.includes(BUILT_IN_MODEL_TAG), @@ -530,7 +530,7 @@ export const ModelsList: FC = ({ )} {showTestFlyout === null ? null : ( )} diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/index_input.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/index_input.tsx new file mode 100644 index 0000000000000..03b506713c312 --- /dev/null +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/index_input.tsx @@ -0,0 +1,204 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useState, useMemo, useEffect, useCallback } from 'react'; + +import useObservable from 'react-use/lib/useObservable'; +import { firstValueFrom } from 'rxjs'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { EuiSpacer, EuiSelect, EuiFormRow, EuiAccordion, EuiCodeBlock } from '@elastic/eui'; + +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { i18n } from '@kbn/i18n'; +import { useMlKibana } from '../../../../contexts/kibana'; +import { RUNNING_STATE } from './inference_base'; +import type { InferrerType } from '.'; + +interface Props { + inferrer: InferrerType; + data: ReturnType; +} + +export const InferenceInputFormIndexControls: FC = ({ inferrer, data }) => { + const { + dataViewListItems, + fieldNames, + selectedDataViewId, + setSelectedDataViewId, + selectedField, + setSelectedField, + } = data; + + const runningState = useObservable(inferrer.getRunningState$()); + const inputComponent = useMemo(() => inferrer.getInputComponent(), [inferrer]); + + return ( + <> + + setSelectedDataViewId(e.target.value)} + hasNoInitialSelection={true} + disabled={runningState === RUNNING_STATE.RUNNING} + /> + + + + setSelectedField(e.target.value)} + hasNoInitialSelection={true} + disabled={runningState === RUNNING_STATE.RUNNING} + /> + + + <>{inputComponent} + + + + + + {JSON.stringify(inferrer.getPipeline(), null, 2)} + + + + ); +}; + +export function useIndexInput({ inferrer }: { inferrer: InferrerType }) { + const { + services: { + data: { + dataViews, + search: { search }, + }, + }, + } = useMlKibana(); + + const [dataViewListItems, setDataViewListItems] = useState< + Array<{ value: string; text: string }> + >([]); + const [selectedDataViewId, setSelectedDataViewId] = useState(undefined); + const [selectedDataView, setSelectedDataView] = useState(null); + const [fieldNames, setFieldNames] = useState>([]); + const [selectedField, setSelectedField] = useState(undefined); + + useEffect( + function loadDataViewListItems() { + dataViews.getIdsWithTitle().then((items) => { + setDataViewListItems( + items + .sort((a, b) => a.title.localeCompare(b.title)) + .map(({ id, title }) => ({ text: title, value: id })) + ); + }); + }, + [dataViews] + ); + + useEffect( + function loadSelectedDataView() { + inferrer.reset(); + setFieldNames([]); + setSelectedField(undefined); + if (selectedDataViewId !== undefined) { + dataViews.get(selectedDataViewId).then((dv) => setSelectedDataView(dv)); + } + }, + [selectedDataViewId, dataViews, inferrer] + ); + + const loadExamples = useCallback(() => { + inferrer.setInputText([]); + if (selectedField !== undefined && selectedDataView !== null) { + firstValueFrom( + search({ + params: { + index: selectedDataView.getIndexPattern(), + body: { + fields: [selectedField], + query: { + function_score: { + functions: [ + { + random_score: {}, + }, + ], + }, + }, + }, + }, + }) + ).then((resp) => { + const tempExamples = resp.rawResponse.hits.hits + .filter(({ fields }) => isPopulatedObject(fields, [selectedField])) + .map(({ fields }) => fields![selectedField][0]); + inferrer.setInputText(tempExamples); + }); + } + }, [inferrer, selectedField, selectedDataView, search]); + + useEffect( + function loadFieldNames() { + if (selectedDataView !== null) { + const tempFieldNames = selectedDataView.fields + .filter( + ({ displayName, esTypes, count }) => + esTypes && esTypes.includes('text') && !['_id', '_index'].includes(displayName) + ) + .sort((a, b) => a.displayName.localeCompare(b.displayName)) + .map(({ displayName }) => ({ + value: displayName, + text: displayName, + })); + setFieldNames(tempFieldNames); + if (tempFieldNames.length === 1) { + const fieldName = tempFieldNames[0].value; + setSelectedField(fieldName); + inferrer.setInputField(fieldName); + } + } + }, + [selectedDataView, inferrer] + ); + + useEffect( + function loadExamplesAfterFieldChange() { + loadExamples(); + }, + [selectedField, loadExamples] + ); + + function reloadExamples() { + inferrer.reset(); + loadExamples(); + } + + return { + fieldNames, + dataViewListItems, + reloadExamples, + selectedDataViewId, + setSelectedDataViewId, + selectedField, + setSelectedField, + }; +} diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_base.ts b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_base.ts index e1a7f23a0473c..5da5a7aad7b04 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_base.ts +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_base.ts @@ -7,6 +7,7 @@ import { BehaviorSubject } from 'rxjs'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { i18n } from '@kbn/i18n'; import { MLHttpFetchError } from '../../../../../../common/util/errors'; import { SupportedPytorchTasksType } from '../../../../../../common/constants/trained_models'; @@ -17,7 +18,19 @@ export type InferenceType = | SupportedPytorchTasksType | keyof estypes.AggregationsInferenceConfigContainer; +export type InferenceOptions = + | estypes.MlRegressionInferenceOptions + | estypes.MlClassificationInferenceOptions + | estypes.MlTextClassificationInferenceOptions + | estypes.MlZeroShotClassificationInferenceOptions + | estypes.MlFillMaskInferenceOptions + | estypes.MlNerInferenceOptions + | estypes.MlPassThroughInferenceOptions + | estypes.MlTextEmbeddingInferenceOptions + | estypes.MlQuestionAnsweringInferenceUpdateOptions; + const DEFAULT_INPUT_FIELD = 'text_field'; +export const DEFAULT_INFERENCE_TIME_OUT = '30s'; export type FormattedNerResponse = Array<{ value: string; @@ -37,21 +50,29 @@ export enum RUNNING_STATE { FINISHED_WITH_ERRORS, } +export enum INPUT_TYPE { + TEXT, + INDEX, +} + export abstract class InferenceBase { protected abstract readonly inferenceType: InferenceType; protected abstract readonly inferenceTypeLabel: string; - protected readonly inputField: string; - public inputText$ = new BehaviorSubject(''); - public inferenceResult$ = new BehaviorSubject(null); - public inferenceError$ = new BehaviorSubject(null); - public runningState$ = new BehaviorSubject(RUNNING_STATE.STOPPED); + protected inputField: string; + protected readonly modelInputField: string; + private inputText$ = new BehaviorSubject([]); + private inferenceResult$ = new BehaviorSubject(null); + private inferenceError$ = new BehaviorSubject(null); + private runningState$ = new BehaviorSubject(RUNNING_STATE.STOPPED); protected readonly info: string[] = []; constructor( - protected trainedModelsApi: ReturnType, - protected model: estypes.MlTrainedModelConfig + protected readonly trainedModelsApi: ReturnType, + protected readonly model: estypes.MlTrainedModelConfig, + protected readonly inputType: INPUT_TYPE ) { - this.inputField = model.input?.field_names[0] ?? DEFAULT_INPUT_FIELD; + this.modelInputField = model.input?.field_names[0] ?? DEFAULT_INPUT_FIELD; + this.inputField = this.modelInputField; } public setStopped() { @@ -76,31 +97,176 @@ export abstract class InferenceBase { return getInferenceInfoComponent(this.inferenceTypeLabel, this.info); } - protected abstract getInputComponent(): JSX.Element; + public getInputType() { + return this.inputType; + } + + public reset() { + this.setInputField(undefined); + this.inputText$.next([]); + this.inferenceResult$.next(null); + this.inferenceError$.next(null); + this.runningState$.next(RUNNING_STATE.STOPPED); + } + + public setInputField(field: string | undefined) { + this.inputField = field === undefined ? this.modelInputField : field; + } + + public setInputText(text: string[]) { + this.inputText$.next(text); + } + + public getInputText$() { + return this.inputText$.asObservable(); + } + + public getInferenceResult$() { + return this.inferenceResult$.asObservable(); + } + + public getInferenceError$() { + return this.inferenceError$.asObservable(); + } + + public getRunningState$() { + return this.runningState$.asObservable(); + } + + protected abstract getInputComponent(): JSX.Element | null; protected abstract getOutputComponent(): JSX.Element; - protected abstract infer(): Promise; + public async infer() { + return this.inputType === INPUT_TYPE.TEXT ? this.inferText() : this.inferIndex(); + } + + protected abstract inferText(): Promise; + protected abstract inferIndex(): Promise; + + public getPipeline(): estypes.IngestPipeline { + return { + processors: this.getProcessors(), + }; + } + + protected getBasicProcessors( + inferenceConfigOverrides?: InferenceOptions + ): estypes.IngestProcessorContainer[] { + const processor: estypes.IngestProcessorContainer = { + inference: { + model_id: this.model.model_id, + target_field: this.inferenceType, + field_map: { + [this.inputField]: this.modelInputField, + }, + ...(inferenceConfigOverrides && Object.keys(inferenceConfigOverrides).length + ? { inference_config: this.getInferenceConfig(inferenceConfigOverrides) } + : {}), + }, + }; + + return [processor]; + } - protected getInferenceConfig(): estypes.MlInferenceConfigCreateContainer[keyof estypes.MlInferenceConfigCreateContainer] { + protected getInferenceConfig( + inferenceConfigOverrides: InferenceOptions + ): estypes.MlInferenceConfigUpdateContainer { + return { + [this.inferenceType as keyof estypes.MlInferenceConfigUpdateContainer]: { + ...inferenceConfigOverrides, + }, + }; + } + + protected async runInfer( + getInferBody: (inputText: string) => estypes.MlInferTrainedModelRequest['body'], + processResponse: (resp: TRawInferResponse, inputText: string) => TInferResponse + ): Promise { + try { + this.setRunning(); + const inputText = this.inputText$.getValue()[0]; + const body = getInferBody(inputText); + + const resp = (await this.trainedModelsApi.inferTrainedModel( + this.model.model_id, + body, + DEFAULT_INFERENCE_TIME_OUT + )) as unknown as TRawInferResponse; + + const processedResponse = processResponse(resp, inputText); + + this.inferenceResult$.next([processedResponse]); + this.setFinished(); + + return [processedResponse]; + } catch (error) { + this.setFinishedWithErrors(error); + throw error; + } + } + + protected async runPipelineSimulate( + processResponse: (d: estypes.IngestSimulateDocumentSimulation) => TInferResponse + ): Promise { + try { + this.setRunning(); + const { docs } = await this.trainedModelsApi.trainedModelPipelineSimulate( + this.getPipeline(), + this.getPipelineDocs() + ); + const processedResponse = docs.map((d) => processResponse(this.getDocFromResponse(d))); + this.inferenceResult$.next(processedResponse); + this.setFinished(); + return processedResponse; + } catch (error) { + this.setFinishedWithErrors(error); + throw error; + } + } + + protected abstract getProcessors(): estypes.IngestProcessorContainer[]; + + protected getPipelineDocs() { + return this.inputText$.getValue().map((v) => ({ + _source: { + [this.inputField]: v, + }, + })); + } + + private getDefaultInferenceConfig(): estypes.MlInferenceConfigUpdateContainer[keyof estypes.MlInferenceConfigUpdateContainer] { return this.model.inference_config[ - this.inferenceType as keyof estypes.MlInferenceConfigCreateContainer + this.inferenceType as keyof estypes.MlInferenceConfigUpdateContainer ]; } protected getNumTopClassesConfig(defaultOverride = 5) { - const options: estypes.MlInferenceConfigCreateContainer[keyof estypes.MlInferenceConfigCreateContainer] = - this.getInferenceConfig(); + const options: estypes.MlInferenceConfigUpdateContainer[keyof estypes.MlInferenceConfigUpdateContainer] = + this.getDefaultInferenceConfig(); if (options && 'num_top_classes' in options && (options?.num_top_classes ?? 0 > 0)) { return {}; } return { - inference_config: { - [this.inferenceType]: { - num_top_classes: defaultOverride, - }, - }, + num_top_classes: defaultOverride, }; } + + // @ts-expect-error error does not exist in type + protected getDocFromResponse({ doc, error }: estypes.IngestSimulatePipelineSimulation) { + if (doc === undefined) { + if (error) { + this.setFinishedWithErrors(error); + throw Error(error.reason); + } + + throw Error( + i18n.translate('xpack.ml.trainedModels.testModelsFlyout.pipelineSimulate.unknownError', { + defaultMessage: 'Error simulating ingest pipeline', + }) + ); + } + return doc; + } } diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/index.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/index.tsx new file mode 100644 index 0000000000000..ad82c1bc0437d --- /dev/null +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { InferenceInputForm } from './inference_input_form'; diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/index_input.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/index_input.tsx new file mode 100644 index 0000000000000..3aeaa3338f231 --- /dev/null +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/index_input.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useState, useMemo, useCallback } from 'react'; + +import useObservable from 'react-use/lib/useObservable'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiSpacer, + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiLoadingSpinner, + EuiText, +} from '@elastic/eui'; + +import { ErrorMessage } from '../../inference_error'; +import { extractErrorMessage } from '../../../../../../../common'; +import type { InferrerType } from '..'; +import { useIndexInput, InferenceInputFormIndexControls } from '../index_input'; +import { RUNNING_STATE } from '../inference_base'; + +interface Props { + inferrer: InferrerType; +} + +export const IndexInput: FC = ({ inferrer }) => { + const data = useIndexInput({ inferrer }); + const { reloadExamples, selectedField } = data; + + const [errorText, setErrorText] = useState(null); + const runningState = useObservable(inferrer.getRunningState$()); + const examples = useObservable(inferrer.getInputText$()) ?? []; + const outputComponent = useMemo(() => inferrer.getOutputComponent(), [inferrer]); + const infoComponent = useMemo(() => inferrer.getInfoComponent(), [inferrer]); + + const run = useCallback(async () => { + setErrorText(null); + try { + await inferrer.infer(); + } catch (e) { + setErrorText(extractErrorMessage(e)); + } + }, [inferrer]); + + return ( + <> + <>{infoComponent} + + + + + + + + + + + + {runningState === RUNNING_STATE.RUNNING ? : null} + + + + + + + + + + + {errorText !== null || runningState === RUNNING_STATE.FINISHED_WITH_ERRORS ? ( + <> + + + + ) : null} + + {runningState !== RUNNING_STATE.FINISHED + ? examples.map((example) => ( + <> + {example} + + + )) + : null} + + {runningState === RUNNING_STATE.FINISHED ? <>{outputComponent} : null} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/inference_input_form.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/inference_input_form.tsx new file mode 100644 index 0000000000000..31c6c87a2e04e --- /dev/null +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/inference_input_form.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, { FC } from 'react'; + +import { INPUT_TYPE } from '../inference_base'; +import { TextInput } from './text_input'; +import { IndexInput } from './index_input'; +import { InferrerType } from '..'; + +interface Props { + inferrer: InferrerType; + inputType: INPUT_TYPE; +} + +export const InferenceInputForm: FC = ({ inferrer, inputType }) => { + return inputType === INPUT_TYPE.TEXT ? ( + + ) : ( + + ); +}; diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/text_input.tsx similarity index 76% rename from x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form.tsx rename to x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/text_input.tsx index a1d24330c1c44..01914e7fbdf7c 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/inference_input_form/text_input.tsx @@ -5,17 +5,18 @@ * 2.0. */ -import React, { FC, useState, useMemo } from 'react'; +import React, { FC, useState, useMemo, useCallback } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSpacer, EuiButton, EuiTabs, EuiTab } from '@elastic/eui'; -import { extractErrorMessage } from '../../../../../../common/util/errors'; -import { ErrorMessage } from '../inference_error'; -import { OutputLoadingContent } from '../output_loading'; -import { RUNNING_STATE } from './inference_base'; -import { RawOutput } from './raw_output'; -import type { InferrerType } from '.'; + +import { ErrorMessage } from '../../inference_error'; +import { extractErrorMessage } from '../../../../../../../common'; +import type { InferrerType } from '..'; +import { OutputLoadingContent } from '../../output_loading'; +import { RUNNING_STATE } from '../inference_base'; +import { RawOutput } from '../raw_output'; interface Props { inferrer: InferrerType; @@ -26,27 +27,24 @@ enum TAB { RAW, } -export const InferenceInputForm: FC = ({ inferrer }) => { +export const TextInput: FC = ({ inferrer }) => { const [selectedTab, setSelectedTab] = useState(TAB.TEXT); const [errorText, setErrorText] = useState(null); - const runningState = useObservable(inferrer.runningState$); - const inputText = useObservable(inferrer.inputText$); - // eslint-disable-next-line react-hooks/exhaustive-deps - const inputComponent = useMemo(() => inferrer.getInputComponent(), []); - // eslint-disable-next-line react-hooks/exhaustive-deps - const outputComponent = useMemo(() => inferrer.getOutputComponent(), []); - // eslint-disable-next-line react-hooks/exhaustive-deps - const infoComponent = useMemo(() => inferrer.getInfoComponent(), []); + const runningState = useObservable(inferrer.getRunningState$()); + const inputText = useObservable(inferrer.getInputText$()) ?? []; + const inputComponent = useMemo(() => inferrer.getInputComponent(), [inferrer]); + const outputComponent = useMemo(() => inferrer.getOutputComponent(), [inferrer]); + const infoComponent = useMemo(() => inferrer.getInfoComponent(), [inferrer]); - async function run() { + const run = useCallback(async () => { setErrorText(null); try { await inferrer.infer(); } catch (e) { setErrorText(extractErrorMessage(e)); } - } + }, [inferrer]); return ( <> @@ -56,7 +54,7 @@ export const InferenceInputForm: FC = ({ inferrer }) => {
= ({ inferrer }) => { {runningState === RUNNING_STATE.RUNNING ? : null} {errorText !== null || runningState === RUNNING_STATE.FINISHED_WITH_ERRORS ? ( - + <> + + + ) : null} {runningState === RUNNING_STATE.FINISHED ? <>{outputComponent} : null} diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/ner/ner_inference.ts b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/ner/ner_inference.ts index 59e510e7a9928..77e53e5087490 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/ner/ner_inference.ts +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/ner/ner_inference.ts @@ -7,7 +7,8 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { i18n } from '@kbn/i18n'; -import { InferenceBase, InferResponse } from '../inference_base'; +import { InferenceBase, INPUT_TYPE } from '../inference_base'; +import type { InferResponse } from '../inference_base'; import { getGeneralInputComponent } from '../text_input'; import { getNerOutputComponent } from './ner_output'; import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../../common/constants/trained_models'; @@ -31,36 +32,44 @@ export class NerInference extends InferenceBase { }), ]; - public async infer() { - try { - this.setRunning(); - const inputText = this.inputText$.getValue(); - const payload = { docs: [{ [this.inputField]: inputText }] }; - const resp = await this.trainedModelsApi.inferTrainedModel( - this.model.model_id, - payload, - '30s' - ); + protected async inferText() { + return this.runInfer( + (inputText: string) => { + return { docs: [{ [this.inputField]: inputText }] }; + }, + (resp, inputText) => { + return { + response: parseResponse(resp), + rawResponse: resp, + inputText, + }; + } + ); + } - const processedResponse: NerResponse = { - response: parseResponse(resp), - rawResponse: resp, - inputText, + protected async inferIndex() { + return this.runPipelineSimulate((doc) => { + return { + response: parseResponse({ inference_results: [doc._source[this.inferenceType]] }), + rawResponse: doc._source[this.inferenceType], + inputText: doc._source[this.inputField], }; - this.inferenceResult$.next(processedResponse); - this.setFinished(); - return processedResponse; - } catch (error) { - this.setFinishedWithErrors(error); - throw error; - } + }); } - public getInputComponent(): JSX.Element { - const placeholder = i18n.translate('xpack.ml.trainedModels.testModelsFlyout.ner.inputText', { - defaultMessage: 'Enter a phrase to test', - }); - return getGeneralInputComponent(this, placeholder); + protected getProcessors() { + return this.getBasicProcessors(); + } + + public getInputComponent(): JSX.Element | null { + if (this.inputType === INPUT_TYPE.TEXT) { + const placeholder = i18n.translate('xpack.ml.trainedModels.testModelsFlyout.ner.inputText', { + defaultMessage: 'Enter a phrase to test', + }); + return getGeneralInputComponent(this, placeholder); + } else { + return null; + } } public getOutputComponent(): JSX.Element { diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/ner/ner_output.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/ner/ner_output.tsx index 76c154e1000b1..e228e411db275 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/ner/ner_output.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/ner/ner_output.tsx @@ -22,7 +22,8 @@ import { useCurrentEuiTheme, EuiThemeType, } from '../../../../../components/color_range_legend/use_color_range'; -import type { NerInference } from './ner_inference'; +import type { NerInference, NerResponse } from './ner_inference'; +import { INPUT_TYPE } from '../inference_base'; const ICON_PADDING = '2px'; const PROBABILITY_SIG_FIGS = 3; @@ -64,13 +65,30 @@ const UNKNOWN_ENTITY_TYPE = { export const getNerOutputComponent = (inferrer: NerInference) => ; const NerOutput: FC<{ inferrer: NerInference }> = ({ inferrer }) => { - const { euiTheme } = useCurrentEuiTheme(); - const result = useObservable(inferrer.inferenceResult$); + const result = useObservable(inferrer.getInferenceResult$()); if (!result) { return null; } + if (inferrer.getInputType() === INPUT_TYPE.INDEX) { + return ( + <> + {result.map((r) => ( + <> + + + + ))} + + ); + } + + return ; +}; + +const Lines: FC<{ result: NerResponse }> = ({ result }) => { + const { euiTheme } = useCurrentEuiTheme(); const lineSplit: JSX.Element[] = []; result.response.forEach(({ value, entity }) => { if (entity === null) { diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/question_answering/question_answering_inference.ts b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/question_answering/question_answering_inference.ts index f5402f77f89f0..9b1b6a77c0759 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/question_answering/question_answering_inference.ts +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/question_answering/question_answering_inference.ts @@ -5,10 +5,10 @@ * 2.0. */ -import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { BehaviorSubject } from 'rxjs'; import { i18n } from '@kbn/i18n'; -import { InferenceBase, InferResponse } from '../inference_base'; +import { InferenceBase } from '../inference_base'; +import type { InferResponse } from '../inference_base'; import { getQuestionAnsweringInput } from './question_answering_input'; import { getQuestionAnsweringOutputComponent } from './question_answering_output'; import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../../common/constants/trained_models'; @@ -57,46 +57,36 @@ export class QuestionAnsweringInference extends InferenceBase(''); - public async infer() { - try { - this.setRunning(); - const inputText = this.inputText$.getValue(); - const questionText = this.questionText$.value; - const numTopClassesConfig = this.getNumTopClassesConfig()?.inference_config; - - const payload = { - docs: [{ [this.inputField]: inputText }], - inference_config: { - [this.inferenceType]: { - question: questionText, - ...(numTopClassesConfig - ? { - num_top_classes: numTopClassesConfig[this.inferenceType].num_top_classes, - } - : {}), - }, - }, - }; - const resp = (await this.trainedModelsApi.inferTrainedModel( - this.model.model_id, - payload, - '30s' - )) as unknown as RawQuestionAnsweringResponse; - - const processedResponse: QuestionAnsweringResponse = processResponse( - resp, - this.model, - inputText - ); - - this.inferenceResult$.next(processedResponse); - this.setFinished(); - - return processedResponse; - } catch (error) { - this.setFinishedWithErrors(error); - throw error; - } + public async inferText() { + return this.runInfer( + (inputText: string) => { + const question = this.questionText$.value; + return { + docs: [{ [this.inputField]: inputText }], + inference_config: this.getInferenceConfig({ + ...this.getNumTopClassesConfig(), + question, + }), + }; + }, + (resp, inputText) => { + return processResponse(resp, inputText); + } + ); + } + + protected async inferIndex() { + return this.runPipelineSimulate((doc) => { + const pretendRawRequest = { inference_results: [doc._source[this.inferenceType]] }; + const inputText = doc._source[this.inputField]; + + return processResponse(pretendRawRequest, inputText); + }); + } + + protected getProcessors() { + const question = this.questionText$.value; + return this.getBasicProcessors({ ...this.getNumTopClassesConfig(), question }); } public getInputComponent(): JSX.Element { @@ -114,11 +104,7 @@ export class QuestionAnsweringInference extends InferenceBase { inferrer.questionText$.next(questionText); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [questionText]); + }, [questionText, inferrer]); - const runningState = useObservable(inferrer.runningState$); + const runningState = useObservable(inferrer.getRunningState$()); return ( ( <> - - + {inferrer.getInputType() === INPUT_TYPE.TEXT ? ( + <> + + + + ) : null} + ); diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/question_answering/question_answering_output.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/question_answering/question_answering_output.tsx index 95c1533efd709..5683c0e74800d 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/question_answering/question_answering_output.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/question_answering/question_answering_output.tsx @@ -8,7 +8,7 @@ import React, { FC, ReactNode } from 'react'; import useObservable from 'react-use/lib/useObservable'; -import { EuiBadge } from '@elastic/eui'; +import { EuiBadge, EuiHorizontalRule } from '@elastic/eui'; import { useCurrentEuiTheme } from '../../../../../components/color_range_legend/use_color_range'; @@ -25,15 +25,21 @@ export const getQuestionAnsweringOutputComponent = (inferrer: QuestionAnsweringI ); const QuestionAnsweringOutput: FC<{ inferrer: QuestionAnsweringInference }> = ({ inferrer }) => { - const result = useObservable(inferrer.inferenceResult$); - if (!result || result.response.length === 0) { + const result = useObservable(inferrer.getInferenceResult$()); + if (!result) { return null; } - const bestResult = result.response[0]; - const { inputText } = result; - - return <>{insertHighlighting(bestResult, inputText)}; + return ( + <> + {result.map(({ response, inputText }) => ( + <> + <>{insertHighlighting(response[0], inputText)} + + + ))} + + ); }; function insertHighlighting(result: FormattedQuestionAnsweringResult, inputText: string) { diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/raw_output.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/raw_output.tsx index 4a82dcb82aa65..d58bc4513c18a 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/raw_output.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/raw_output.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import React, { FC } from 'react'; import { Observable } from 'rxjs'; import useObservable from 'react-use/lib/useObservable'; @@ -14,16 +15,23 @@ import type { InferrerType } from '.'; import { NerResponse } from './ner'; import { TextClassificationResponse } from './text_classification'; import { TextEmbeddingResponse } from './text_embedding'; -import { RUNNING_STATE } from './inference_base'; +import { INPUT_TYPE, RUNNING_STATE } from './inference_base'; +import { RawTextClassificationResponse } from './text_classification/common'; +import { RawTextEmbeddingResponse } from './text_embedding/text_embedding_inference'; -type InferenceResponse = NerResponse | TextClassificationResponse | TextEmbeddingResponse; +type InferenceResponse = NerResponse[] | TextClassificationResponse[] | TextEmbeddingResponse[]; +type ResultResponses = Array< + estypes.MlInferTrainedModelResponse | RawTextClassificationResponse | RawTextEmbeddingResponse +>; export const RawOutput: FC<{ inferrer: InferrerType; }> = ({ inferrer }) => { - const inferenceError = useObservable(inferrer.inferenceError$); - const runningState = useObservable(inferrer.runningState$); - const inferenceResult = useObservable(inferrer.inferenceResult$ as Observable); + const inferenceError = useObservable(inferrer.getInferenceError$()); + const runningState = useObservable(inferrer.getRunningState$()); + const inferenceResult = useObservable( + inferrer.getInferenceResult$() as Observable + ); if ( (runningState === RUNNING_STATE.FINISHED_WITH_ERRORS && !inferenceError) || @@ -32,10 +40,21 @@ export const RawOutput: FC<{ return null; } + const resultResponse: ResultResponses = []; + if (inferenceResult) { + for (const { rawResponse } of inferenceResult) { + resultResponse.push(rawResponse); + } + } + const rawResponse = runningState === RUNNING_STATE.FINISHED_WITH_ERRORS ? JSON.stringify(inferenceError?.body ?? inferenceError, null, 2) - : JSON.stringify(inferenceResult?.rawResponse, null, 2); + : JSON.stringify( + inferrer.getInputType() === INPUT_TYPE.TEXT ? resultResponse[0] : resultResponse, + null, + 2 + ); return ( <> diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/common.ts b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/common.ts index 6592e44800d14..e1a0ae6438e11 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/common.ts +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/common.ts @@ -9,18 +9,20 @@ import { InferResponse } from '../inference_base'; const PROBABILITY_SIG_FIGS = 3; -export interface RawTextClassificationResponse { - inference_results: Array<{ - predicted_value: string; - prediction_probability: number; - top_classes?: Array<{ - class_name: string; - class_probability: number; - class_score: number; - }>; +export interface InferenceResult { + predicted_value: string; + prediction_probability: number; + top_classes?: Array<{ + class_name: string; + class_probability: number; + class_score: number; }>; } +export interface RawTextClassificationResponse { + inference_results: InferenceResult[]; +} + export type FormattedTextClassificationResponse = Array<{ value: string; predictionProbability: number; @@ -37,21 +39,35 @@ export function processResponse( inputText: string ): TextClassificationResponse { const { - inference_results: [inferenceResults], + inference_results: [inferenceResult], } = resp; + + const formattedResponse = processInferenceResult(inferenceResult, model); + + return { + response: formattedResponse, + rawResponse: resp, + inputText, + }; +} + +export function processInferenceResult( + inferenceResult: InferenceResult, + model: estypes.MlTrainedModelConfig +): FormattedTextClassificationResponse { const labels: string[] = model.inference_config.text_classification?.classification_labels ?? []; let formattedResponse = [ { - value: inferenceResults.predicted_value, - predictionProbability: inferenceResults.prediction_probability, + value: inferenceResult.predicted_value, + predictionProbability: inferenceResult.prediction_probability, }, ]; - if (inferenceResults.top_classes !== undefined) { + if (inferenceResult.top_classes !== undefined) { // if num_top_classes has been specified in the model, // base the returned results on this list - formattedResponse = inferenceResults.top_classes.map((topClass) => { + formattedResponse = inferenceResult.top_classes.map((topClass) => { return { value: topClass.class_name, predictionProbability: topClass.class_probability, @@ -62,9 +78,9 @@ export function processResponse( // we can safely assume the non-top value and return two results formattedResponse = labels.map((value) => { const predictionProbability = - inferenceResults.predicted_value === value - ? inferenceResults.prediction_probability - : 1 - inferenceResults.prediction_probability; + inferenceResult.predicted_value === value + ? inferenceResult.prediction_probability + : 1 - inferenceResult.prediction_probability; return { value, @@ -73,15 +89,11 @@ export function processResponse( }); } - return { - response: formattedResponse - .map(({ value, predictionProbability }) => ({ - value, - predictionProbability: Number(predictionProbability.toPrecision(PROBABILITY_SIG_FIGS)), - })) - .sort((a, b) => a.predictionProbability - b.predictionProbability) - .reverse(), - rawResponse: resp, - inputText, - }; + return formattedResponse + .map(({ value, predictionProbability }) => ({ + value, + predictionProbability: Number(predictionProbability.toPrecision(PROBABILITY_SIG_FIGS)), + })) + .sort((a, b) => a.predictionProbability - b.predictionProbability) + .reverse(); } diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/fill_mask_inference.ts b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/fill_mask_inference.ts index c479e98a532e6..c0275d24d638d 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/fill_mask_inference.ts +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/fill_mask_inference.ts @@ -6,9 +6,9 @@ */ import { i18n } from '@kbn/i18n'; -import { InferenceBase } from '../inference_base'; +import { InferenceBase, INPUT_TYPE } from '../inference_base'; import type { TextClassificationResponse, RawTextClassificationResponse } from './common'; -import { processResponse } from './common'; +import { processResponse, processInferenceResult } from './common'; import { getGeneralInputComponent } from '../text_input'; import { getFillMaskOutputComponent } from './fill_mask_output'; import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../../common/constants/trained_models'; @@ -28,51 +28,53 @@ export class FillMaskInference extends InferenceBase }), ]; - public async infer() { - try { - this.setRunning(); - const inputText = this.inputText$.getValue(); - const payload = { - docs: [{ [this.inputField]: inputText }], - ...this.getNumTopClassesConfig(), - }; - const resp = (await this.trainedModelsApi.inferTrainedModel( - this.model.model_id, - payload, - '30s' - )) as unknown as RawTextClassificationResponse; + protected async inferText() { + return this.runInfer( + (inputText: string) => { + return { + docs: [{ [this.inputField]: inputText }], + inference_config: this.getInferenceConfig(this.getNumTopClassesConfig()), + }; + }, + (resp, inputText) => { + return processResponse(resp, this.model, inputText); + } + ); + } - const processedResponse = processResponse(resp, this.model, inputText); - this.inferenceResult$.next(processedResponse); - this.setFinished(); + protected async inferIndex() { + return this.runPipelineSimulate((doc) => { + return { + response: processInferenceResult(doc._source[this.inferenceType], this.model), + rawResponse: doc._source[this.inferenceType], + inputText: doc._source[this.inputField], + }; + }); + } - return processedResponse; - } catch (error) { - this.setFinishedWithErrors(error); - throw error; - } + protected getProcessors() { + return this.getBasicProcessors(this.getNumTopClassesConfig()); } - public predictedValue() { - const result = this.inferenceResult$.value; - if (result === null) { - return ''; - } - return result.response[0]?.value - ? result.inputText.replace(MASK, result.response[0].value) - : result.inputText; + public predictedValue(resp: TextClassificationResponse) { + const { response, inputText } = resp; + return response[0]?.value ? inputText.replace(MASK, response[0].value) : inputText; } - public getInputComponent(): JSX.Element { - const placeholder = i18n.translate( - 'xpack.ml.trainedModels.testModelsFlyout.fillMask.inputText', - { - defaultMessage: - 'Enter a phrase to test. Use [MASK] as a placeholder for the missing words.', - } - ); + public getInputComponent(): JSX.Element | null { + if (this.inputType === INPUT_TYPE.TEXT) { + const placeholder = i18n.translate( + 'xpack.ml.trainedModels.testModelsFlyout.fillMask.inputText', + { + defaultMessage: + 'Enter a phrase to test. Use [MASK] as a placeholder for the missing words.', + } + ); - return getGeneralInputComponent(this, placeholder); + return getGeneralInputComponent(this, placeholder); + } else { + return null; + } } public getOutputComponent(): JSX.Element { diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/fill_mask_output.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/fill_mask_output.tsx index 9c92d6714b613..1044aceccb274 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/fill_mask_output.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/fill_mask_output.tsx @@ -5,12 +5,12 @@ * 2.0. */ -import React, { FC, useMemo } from 'react'; +import React, { FC } from 'react'; import useObservable from 'react-use/lib/useObservable'; -import { EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiHorizontalRule } from '@elastic/eui'; import type { FillMaskInference } from './fill_mask_inference'; -import { TextClassificationOutput } from './text_classification_output'; +import { PredictionProbabilityList } from './text_classification_output'; export const getFillMaskOutputComponent = (inferrer: FillMaskInference) => ( @@ -19,9 +19,7 @@ export const getFillMaskOutputComponent = (inferrer: FillMaskInference) => ( const FillMaskOutput: FC<{ inferrer: FillMaskInference; }> = ({ inferrer }) => { - const result = useObservable(inferrer.inferenceResult$); - // eslint-disable-next-line react-hooks/exhaustive-deps - const title = useMemo(() => inferrer.predictedValue(), []); + const result = useObservable(inferrer.getInferenceResult$()); if (!result) { return null; @@ -29,12 +27,15 @@ const FillMaskOutput: FC<{ return ( <> - -

{title}

-
- - - + {result.map((res) => ( + <> + + + + ))} ); }; diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/lang_ident_inference.ts b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/lang_ident_inference.ts index 8260347b7f495..6defaa644bbe5 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/lang_ident_inference.ts +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/lang_ident_inference.ts @@ -6,8 +6,9 @@ */ import { i18n } from '@kbn/i18n'; -import { InferenceBase, InferenceType } from '../inference_base'; -import { processResponse } from './common'; +import { InferenceBase, INPUT_TYPE } from '../inference_base'; +import type { InferenceType } from '../inference_base'; +import { processInferenceResult, processResponse } from './common'; import { getGeneralInputComponent } from '../text_input'; import { getLangIdentOutputComponent } from './lang_ident_output'; import type { TextClassificationResponse, RawTextClassificationResponse } from './common'; @@ -24,43 +25,56 @@ export class LangIdentInference extends InferenceBase( + (inputText: string) => { + return { + docs: [{ [this.inputField]: inputText }], + inference_config: this.getInferenceConfig(this.getNumTopClassesConfig()), + }; + }, + (resp, inputText) => { + return processResponse(resp, this.model, inputText); + } ); - this.inferenceResult$.next(processedResponse); - this.setFinished(); + } catch (error) { + this.setFinishedWithErrors(error); + throw error; + } + } - return processedResponse; + protected async inferIndex() { + try { + return await this.runPipelineSimulate((doc) => { + return { + response: processInferenceResult(doc._source[this.inferenceType], this.model), + rawResponse: doc._source[this.inferenceType], + inputText: doc._source[this.inputField], + }; + }); } catch (error) { this.setFinishedWithErrors(error); throw error; } } - public getInputComponent(): JSX.Element { - const placeholder = i18n.translate( - 'xpack.ml.trainedModels.testModelsFlyout.langIdent.inputText', - { - defaultMessage: 'Enter a phrase to test', - } - ); - return getGeneralInputComponent(this, placeholder); + protected getProcessors() { + return this.getBasicProcessors(this.getNumTopClassesConfig()); + } + + public getInputComponent(): JSX.Element | null { + if (this.inputType === INPUT_TYPE.TEXT) { + const placeholder = i18n.translate( + 'xpack.ml.trainedModels.testModelsFlyout.langIdent.inputText', + { + defaultMessage: 'Enter a phrase to test', + } + ); + return getGeneralInputComponent(this, placeholder); + } else { + return null; + } } public getOutputComponent(): JSX.Element { diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/lang_ident_output.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/lang_ident_output.tsx index a4f2a6e2884e1..b0edf4586a00a 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/lang_ident_output.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/lang_ident_output.tsx @@ -8,23 +8,41 @@ import React, { FC } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiHorizontalRule, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import type { LangIdentInference } from './lang_ident_inference'; import { getLanguage } from './lang_codes'; -import { getTextClassificationOutputComponent } from './text_classification_output'; +import { PredictionProbabilityList } from './text_classification_output'; +import { FormattedTextClassificationResponse } from './common'; export const getLangIdentOutputComponent = (inferrer: LangIdentInference) => ( ); const LangIdentOutput: FC<{ inferrer: LangIdentInference }> = ({ inferrer }) => { - const result = useObservable(inferrer.inferenceResult$); - if (!result || result.response.length === 0) { + const result = useObservable(inferrer.getInferenceResult$()); + if (!result) { return null; } - const lang = getLanguage(result.response[0].value); + return ( + <> + {result.map(({ response, inputText }) => ( + <> + + + + ))} + + ); +}; + +const LanguageIdent: FC<{ + response: FormattedTextClassificationResponse; + inputText: string; +}> = ({ response, inputText }) => { + const langCode = response[0].value; + const lang = getLanguage(langCode); const title = lang !== 'unknown' @@ -33,18 +51,20 @@ const LangIdentOutput: FC<{ inferrer: LangIdentInference }> = ({ inferrer }) => values: { lang }, }) : i18n.translate('xpack.ml.trainedModels.testModelsFlyout.langIdent.output.titleUnknown', { - defaultMessage: 'Language code unknown: {code}', - values: { code: result.response[0].value }, + defaultMessage: 'Language code unknown: {langCode}', + values: { langCode }, }); return ( <> - + {inputText} + +

{title}

- {getTextClassificationOutputComponent(inferrer)} + ); }; diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/text_classification_inference.ts b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/text_classification_inference.ts index 162539ab754b5..48193c1abc180 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/text_classification_inference.ts +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/text_classification_inference.ts @@ -6,8 +6,8 @@ */ import { i18n } from '@kbn/i18n'; -import { InferenceBase } from '../inference_base'; -import { processResponse } from './common'; +import { InferenceBase, INPUT_TYPE } from '../inference_base'; +import { processInferenceResult, processResponse } from './common'; import type { TextClassificationResponse, RawTextClassificationResponse } from './common'; import { getGeneralInputComponent } from '../text_input'; import { getTextClassificationOutputComponent } from './text_classification_output'; @@ -25,43 +25,46 @@ export class TextClassificationInference extends InferenceBase( + (inputText: string) => { + return { + docs: [{ [this.inputField]: inputText }], + inference_config: this.getInferenceConfig(this.getNumTopClassesConfig()), + }; + }, + (resp, inputText) => { + return processResponse(resp, this.model, inputText); + } + ); + } - const processedResponse: TextClassificationResponse = processResponse( - resp, - this.model, - inputText - ); - this.inferenceResult$.next(processedResponse); - this.setFinished(); + protected async inferIndex() { + return this.runPipelineSimulate((doc) => { + return { + response: processInferenceResult(doc._source[this.inferenceType], this.model), + rawResponse: doc._source[this.inferenceType], + inputText: doc._source[this.inputField], + }; + }); + } - return processedResponse; - } catch (error) { - this.setFinishedWithErrors(error); - throw error; - } + protected getProcessors() { + return this.getBasicProcessors(this.getNumTopClassesConfig()); } - public getInputComponent(): JSX.Element { - const placeholder = i18n.translate( - 'xpack.ml.trainedModels.testModelsFlyout.textClassification.inputText', - { - defaultMessage: 'Enter a phrase to test', - } - ); - return getGeneralInputComponent(this, placeholder); + public getInputComponent(): JSX.Element | null { + if (this.inputType === INPUT_TYPE.TEXT) { + const placeholder = i18n.translate( + 'xpack.ml.trainedModels.testModelsFlyout.textClassification.inputText', + { + defaultMessage: 'Enter a phrase to test', + } + ); + return getGeneralInputComponent(this, placeholder); + } else { + return null; + } } public getOutputComponent(): JSX.Element { diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/text_classification_output.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/text_classification_output.tsx index 69ecf621510af..8427000e92a77 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/text_classification_output.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/text_classification_output.tsx @@ -7,13 +7,21 @@ import React, { FC } from 'react'; import useObservable from 'react-use/lib/useObservable'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiProgress } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiProgress, + EuiTitle, + EuiHorizontalRule, +} from '@elastic/eui'; import type { TextClassificationInference, ZeroShotClassificationInference, FillMaskInference, LangIdentInference, + FormattedTextClassificationResponse, } from '.'; export const getTextClassificationOutputComponent = ( @@ -31,13 +39,39 @@ export const TextClassificationOutput: FC<{ | FillMaskInference | LangIdentInference; }> = ({ inferrer }) => { - const result = useObservable(inferrer.inferenceResult$); + const result = useObservable(inferrer.getInferenceResult$()); if (!result) { return null; } + + return ( + <> + {result.map(({ response, inputText }) => ( + <> + + + + ))} + + ); +}; + +export const PredictionProbabilityList: FC<{ + response: FormattedTextClassificationResponse; + inputText?: string; +}> = ({ response, inputText }) => { return ( <> - {result.response.map(({ value, predictionProbability }) => ( + {inputText !== undefined ? ( + <> + + {inputText} + + + + ) : null} + + {response.map(({ value, predictionProbability }) => ( <> diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/zero_shot_classification_inference.ts b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/zero_shot_classification_inference.ts index 35db0c85f7337..6b0f61ad119be 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/zero_shot_classification_inference.ts +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/zero_shot_classification_inference.ts @@ -7,8 +7,9 @@ import { i18n } from '@kbn/i18n'; import { BehaviorSubject } from 'rxjs'; +import { estypes } from '@elastic/elasticsearch'; import { InferenceBase } from '../inference_base'; -import { processResponse } from './common'; +import { processInferenceResult, processResponse } from './common'; import type { TextClassificationResponse, RawTextClassificationResponse } from './common'; import { getZeroShotClassificationInput } from './zero_shot_classification_input'; @@ -30,40 +31,46 @@ export class ZeroShotClassificationInference extends InferenceBase(''); - public async infer() { - try { - this.setRunning(); - const inputText = this.inputText$.getValue(); - const labelsText = this.labelsText$.value; - const inputLabels = labelsText?.split(',').map((l) => l.trim()); - const payload = { - docs: [{ [this.inputField]: inputText }], - inference_config: { - [this.inferenceType]: { + public async inferText() { + return this.runInfer( + (inputText: string) => { + const labelsText = this.labelsText$.getValue(); + const inputLabels = labelsText?.split(',').map((l) => l.trim()); + return { + docs: [{ [this.inputField]: inputText }], + inference_config: this.getInferenceConfig({ labels: inputLabels, multi_label: false, - }, - }, + } as estypes.MlZeroShotClassificationInferenceUpdateOptions), + }; + }, + (resp, inputText) => { + return processResponse(resp, this.model, inputText); + } + ); + } + + protected async inferIndex() { + return this.runPipelineSimulate((doc) => { + return { + response: processInferenceResult(doc._source[this.inferenceType], this.model), + rawResponse: doc._source[this.inferenceType], + inputText: doc._source[this.inputField], }; - const resp = (await this.trainedModelsApi.inferTrainedModel( - this.model.model_id, - payload, - '30s' - )) as unknown as RawTextClassificationResponse; + }); + } - const processedResponse: TextClassificationResponse = processResponse( - resp, - this.model, - inputText - ); - this.inferenceResult$.next(processedResponse); - this.setFinished(); + private getInputLabels() { + const labelsText = this.labelsText$.getValue(); + return labelsText?.split(',').map((l) => l.trim()); + } - return processedResponse; - } catch (error) { - this.setFinishedWithErrors(error); - throw error; - } + protected getProcessors() { + const inputLabels = this.getInputLabels(); + return this.getBasicProcessors({ + labels: inputLabels, + multi_label: false, + } as estypes.MlZeroShotClassificationInferenceUpdateOptions); } public getInputComponent(): JSX.Element { diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/zero_shot_classification_input.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/zero_shot_classification_input.tsx index adccbd99e2d75..791ad9f845315 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/zero_shot_classification_input.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_classification/zero_shot_classification_input.tsx @@ -13,7 +13,7 @@ import { EuiSpacer, EuiFieldText, EuiFormRow } from '@elastic/eui'; import { TextInput } from '../text_input'; import { ZeroShotClassificationInference } from './zero_shot_classification_inference'; -import { RUNNING_STATE } from '../inference_base'; +import { INPUT_TYPE, RUNNING_STATE } from '../inference_base'; const ClassNameInput: FC<{ inferrer: ZeroShotClassificationInference; @@ -22,10 +22,9 @@ const ClassNameInput: FC<{ useEffect(() => { inferrer.labelsText$.next(labelsText); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [labelsText]); + }, [labelsText, inferrer]); - const runningState = useObservable(inferrer.runningState$); + const runningState = useObservable(inferrer.getRunningState$()); return ( ( <> - - + {inferrer.getInputType() === INPUT_TYPE.TEXT ? ( + <> + + + + ) : null} ); diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_embedding/text_embedding_inference.ts b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_embedding/text_embedding_inference.ts index 18bde4597d12b..767794e84d9c8 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_embedding/text_embedding_inference.ts +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_embedding/text_embedding_inference.ts @@ -5,16 +5,15 @@ * 2.0. */ -import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; - import { i18n } from '@kbn/i18n'; -import { InferenceBase, InferResponse } from '../inference_base'; +import { InferenceBase, INPUT_TYPE } from '../inference_base'; +import type { InferResponse } from '../inference_base'; import { getGeneralInputComponent } from '../text_input'; import { getTextEmbeddingOutputComponent } from './text_embedding_output'; import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../../common/constants/trained_models'; export interface RawTextEmbeddingResponse { - inference_results: [{ predicted_value: number[] }]; + inference_results: Array<{ predicted_value: number[] }>; } export interface FormattedTextEmbeddingResponse { @@ -38,38 +37,42 @@ export class TextEmbeddingInference extends InferenceBase }), ]; - public async infer() { - try { - this.setRunning(); - const inputText = this.inputText$.getValue(); - const payload = { - docs: [{ [this.inputField]: inputText }], - }; - const resp = (await this.trainedModelsApi.inferTrainedModel( - this.model.model_id, - payload, - '30s' - )) as unknown as RawTextEmbeddingResponse; + public async inferText() { + return this.runInfer( + (inputText: string) => { + return { + docs: [{ [this.inputField]: inputText }], + }; + }, + (resp, inputText) => { + return processTextResponse(resp, inputText); + } + ); + } - const processedResponse: TextEmbeddingResponse = processResponse(resp, this.model, inputText); - this.inferenceResult$.next(processedResponse); - this.setFinished(); + protected async inferIndex() { + return this.runPipelineSimulate((doc) => { + const inputText = doc._source[this.inputField]; + return processIndexResponse(doc._source[this.inferenceType], inputText); + }); + } - return processedResponse; - } catch (error) { - this.setFinishedWithErrors(error); - throw error; - } + protected getProcessors() { + return this.getBasicProcessors(); } - public getInputComponent(): JSX.Element { - const placeholder = i18n.translate( - 'xpack.ml.trainedModels.testModelsFlyout.textEmbedding.inputText', - { - defaultMessage: 'Enter a phrase to test', - } - ); - return getGeneralInputComponent(this, placeholder); + public getInputComponent(): JSX.Element | null { + if (this.inputType === INPUT_TYPE.TEXT) { + const placeholder = i18n.translate( + 'xpack.ml.trainedModels.testModelsFlyout.textEmbedding.inputText', + { + defaultMessage: 'Enter a phrase to test', + } + ); + return getGeneralInputComponent(this, placeholder); + } else { + return null; + } } public getOutputComponent(): JSX.Element { @@ -77,11 +80,20 @@ export class TextEmbeddingInference extends InferenceBase } } -function processResponse( +function processTextResponse( resp: RawTextEmbeddingResponse, - model: estypes.MlTrainedModelConfig, + inputText: string ) { const predictedValue = resp.inference_results[0].predicted_value; return { response: { predictedValue }, rawResponse: resp, inputText }; } + +function processIndexResponse(resp: { predicted_value: number[] }, inputText: string) { + const predictedValue = resp.predicted_value; + return { + response: { predictedValue }, + rawResponse: { inference_results: [{ predicted_value: predictedValue }] }, + inputText, + }; +} diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_embedding/text_embedding_output.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_embedding/text_embedding_output.tsx index bb60140d39e68..340318f11c86f 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_embedding/text_embedding_output.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_embedding/text_embedding_output.tsx @@ -8,7 +8,14 @@ import React, { FC } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiTextArea, EuiCopy, EuiButton } from '@elastic/eui'; +import { + EuiTextArea, + EuiCopy, + EuiButton, + EuiHorizontalRule, + EuiText, + EuiSpacer, +} from '@elastic/eui'; import type { TextEmbeddingInference } from './text_embedding_inference'; @@ -19,14 +26,31 @@ export const getTextEmbeddingOutputComponent = (inferrer: TextEmbeddingInference const TextEmbeddingOutput: FC<{ inferrer: TextEmbeddingInference; }> = ({ inferrer }) => { - const result = useObservable(inferrer.inferenceResult$); + const result = useObservable(inferrer.getInferenceResult$()); if (!result) { return null; } - const value = result.response.predictedValue.toString(); return ( <> + {result.map(({ response, inputText }) => ( + <> + + + + ))} + + ); +}; + +const TextEmbedding: FC<{ + value: string; + inputText: string; +}> = ({ value, inputText }) => { + return ( + <> + {inputText} + {(copy) => ( diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_input.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_input.tsx index ca4af60e87856..d0c3cdc71568e 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_input.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_input.tsx @@ -19,11 +19,10 @@ export const TextInput: FC<{ const [inputText, setInputText] = useState(''); useEffect(() => { - inferrer.inputText$.next(inputText); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [inputText]); + inferrer.setInputText([inputText]); + }, [inputText, inferrer]); - const runningState = useObservable(inferrer.runningState$); + const runningState = useObservable(inferrer.getRunningState$()); return ( = ({ model }) => { +export const SelectedModel: FC = ({ model, inputType }) => { const { trainedModels } = useMlApiContext(); - const inferrer = useMemo(() => { + const inferrer: InferrerType | undefined = useMemo(() => { if (model.model_type === TRAINED_MODEL_TYPE.PYTORCH) { const taskType = Object.keys(model.inference_config)[0]; switch (taskType) { case SUPPORTED_PYTORCH_TASKS.NER: - return new NerInference(trainedModels, model); + return new NerInference(trainedModels, model, inputType); + break; case SUPPORTED_PYTORCH_TASKS.TEXT_CLASSIFICATION: - return new TextClassificationInference(trainedModels, model); + return new TextClassificationInference(trainedModels, model, inputType); + break; case SUPPORTED_PYTORCH_TASKS.ZERO_SHOT_CLASSIFICATION: - return new ZeroShotClassificationInference(trainedModels, model); + return new ZeroShotClassificationInference(trainedModels, model, inputType); + break; case SUPPORTED_PYTORCH_TASKS.TEXT_EMBEDDING: - return new TextEmbeddingInference(trainedModels, model); + return new TextEmbeddingInference(trainedModels, model, inputType); + break; case SUPPORTED_PYTORCH_TASKS.FILL_MASK: - return new FillMaskInference(trainedModels, model); + return new FillMaskInference(trainedModels, model, inputType); + break; case SUPPORTED_PYTORCH_TASKS.QUESTION_ANSWERING: - return new QuestionAnsweringInference(trainedModels, model); + return new QuestionAnsweringInference(trainedModels, model, inputType); + break; default: break; } } else if (model.model_type === TRAINED_MODEL_TYPE.LANG_IDENT) { - return new LangIdentInference(trainedModels, model); + return new LangIdentInference(trainedModels, model, inputType); } - }, [model, trainedModels]); + }, [inputType, model, trainedModels]); - if (inferrer === undefined) { - return null; + if (inferrer !== undefined) { + return ; } - return ; + return null; }; diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/test_flyout.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/test_flyout.tsx index 343cd32addce7..6804a654711cf 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/test_flyout.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/test_flyout.tsx @@ -6,18 +6,50 @@ */ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import React, { FC } from 'react'; +import React, { FC, useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody, EuiSpacer } from '@elastic/eui'; +import { + EuiFlyout, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiSpacer, + EuiTab, + EuiTabs, + useEuiPaddingSize, +} from '@elastic/eui'; import { SelectedModel } from './selected_model'; +import { INPUT_TYPE } from './models/inference_base'; +import { useTrainedModelsApiService } from '../../../services/ml_api_service/trained_models'; interface Props { - model: estypes.MlTrainedModelConfig; + modelId: string; onClose: () => void; } -export const TestTrainedModelFlyout: FC = ({ model, onClose }) => { +export const TestTrainedModelFlyout: FC = ({ modelId, onClose }) => { + const mediumPadding = useEuiPaddingSize('m'); + + const trainedModelsApiService = useTrainedModelsApiService(); + const [inputType, setInputType] = useState(INPUT_TYPE.TEXT); + const [model, setModel] = useState(null); + + useEffect( + function fetchModel() { + trainedModelsApiService.getTrainedModels(modelId).then((resp) => { + if (resp.length) { + setModel(resp[0]); + } + }); + }, + [modelId, trainedModelsApiService] + ); + + if (model === null) { + return null; + } + return ( <> @@ -30,15 +62,41 @@ export const TestTrainedModelFlyout: FC = ({ model, onClose }) => { />
- - +

{model.model_id}

+ + + + setInputType(INPUT_TYPE.TEXT)} + > + + + setInputType(INPUT_TYPE.INDEX)} + > + + + - + diff --git a/x-pack/plugins/ml/public/locator/ml_locator.ts b/x-pack/plugins/ml/public/locator/ml_locator.ts index a742700cceaec..6860720d45142 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 6bb6da5b09069..e34afb42cde9d 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/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json index dbe45dfdb7a6e..e265f2c828f09 100644 --- a/x-pack/plugins/ml/server/routes/apidoc.json +++ b/x-pack/plugins/ml/server/routes/apidoc.json @@ -177,6 +177,7 @@ "StopTrainedModelDeployment", "PutTrainedModel", "DeleteTrainedModel", + "SimulateIngestPipeline", "InferTrainedModelDeployment", "Alerting", diff --git a/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts b/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts index d73d479e7aac3..dd0a15fa20f83 100644 --- a/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts @@ -48,3 +48,9 @@ export const inferTrainedModelBody = schema.object({ docs: schema.any(), inference_config: schema.maybe(schema.any()), }); + +export const pipelineSimulateBody = schema.object({ + pipeline: schema.any(), + docs: schema.arrayOf(schema.any()), +}); +export const pipelineDocs = schema.arrayOf(schema.string()); diff --git a/x-pack/plugins/ml/server/routes/trained_models.ts b/x-pack/plugins/ml/server/routes/trained_models.ts index 29a39ecfbf7e4..a234163dbde99 100644 --- a/x-pack/plugins/ml/server/routes/trained_models.ts +++ b/x-pack/plugins/ml/server/routes/trained_models.ts @@ -16,6 +16,7 @@ import { inferTrainedModelQuery, inferTrainedModelBody, threadingParamsSchema, + pipelineSimulateBody, updateDeploymentParamsSchema, } from './schemas/inference_schema'; import { modelsProvider } from '../models/data_frame_analytics'; @@ -393,6 +394,39 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) }) ); + /** + * @apiGroup TrainedModels + * + * @api {post} /api/ml/trained_models/pipeline_simulate Simulates an ingest pipeline + * @apiName SimulateIngestPipeline + * @apiDescription Simulates an ingest pipeline. + */ + router.post( + { + path: '/api/ml/trained_models/pipeline_simulate', + validate: { + body: pipelineSimulateBody, + }, + options: { + tags: ['access:ml:canTestTrainedModels'], + }, + }, + routeGuard.fullLicenseAPIGuard(async ({ client, request, response }) => { + try { + const { pipeline, docs } = request.body; + const body = await client.asInternalUser.ingest.simulate({ + pipeline, + docs, + }); + return response.ok({ + body, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + /** * @apiGroup TrainedModels * 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 0000000000000..bdf565a29cbdb --- /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 258cc99057730..d76ebbfd4b452 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 7c96dac50c37e..b9e3c706b18a0 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 c7fb176bbb65d..373b57e0e61af 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 2cd8487c57048..ea414260a4380 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 59b352c27e5d8..51e89570bc241 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 fe79b0650d521..31aa38cb2fd49 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 935a148c58667..bc38a46046fd4 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/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 804fa4bcdd4a6..91c8c51704718 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 f527956d5c7fa..e5b38430f9fdc 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 1c4cfe4639b1a..9fdacb220dc56 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 e01ab0105a5d5..e546f339d2b88 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 82994950dfd04..aeebe987e20de 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/authentication/api_keys/api_keys.mock.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.mock.ts index e82efeb5168d9..0c18119ad2051 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.mock.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.mock.ts @@ -14,6 +14,7 @@ export const apiKeysMock = { areAPIKeysEnabled: jest.fn(), create: jest.fn(), grantAsInternalUser: jest.fn(), + validate: jest.fn(), invalidate: jest.fn(), invalidateAsInternalUser: jest.fn(), }), diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts index 2aa318acff59d..8c757cd9cfc0c 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.test.ts @@ -18,6 +18,7 @@ import { ALL_SPACES_ID } from '../../../common/constants'; import type { SecurityLicense } from '../../../common/licensing'; import { licenseMock } from '../../../common/licensing/index.mock'; import { APIKeys } from './api_keys'; +import { getFakeKibanaRequest } from './fake_kibana_request'; const encodeToBase64 = (str: string) => Buffer.from(str).toString('base64'); @@ -407,6 +408,51 @@ describe('API Keys', () => { }); }); + describe('validate()', () => { + it('returns false when security feature is disabled', async () => { + mockLicense.isEnabled.mockReturnValue(false); + const result = await apiKeys.validate({ + id: '123', + api_key: 'abc123', + }); + expect(result).toEqual(false); + expect(mockClusterClient.asScoped).not.toHaveBeenCalled(); + }); + + it('calls callCluster with proper parameters', async () => { + mockLicense.isEnabled.mockReturnValue(true); + const params = { + id: '123', + api_key: 'abc123', + }; + const result = await apiKeys.validate(params); + expect(result).toEqual(true); + + const fakeRequest = getFakeKibanaRequest(params); + + const { id, uuid, ...restFake } = fakeRequest; + + expect(mockClusterClient.asScoped).toHaveBeenCalledWith(expect.objectContaining(restFake)); + expect( + mockClusterClient.asScoped().asCurrentUser.security.authenticate + ).toHaveBeenCalledWith(); + }); + + it('returns false if cannot authenticate with the API key', async () => { + mockLicense.isEnabled.mockReturnValue(true); + mockScopedClusterClient.asCurrentUser.security.authenticate.mockRejectedValue(new Error()); + const params = { id: '123', api_key: 'abc123' }; + + await expect(apiKeys.validate(params)).resolves.toEqual(false); + + const { id, uuid, ...restFake } = getFakeKibanaRequest(params); + expect(mockClusterClient.asScoped).toHaveBeenCalledWith(expect.objectContaining(restFake)); + expect( + mockClusterClient.asScoped().asCurrentUser.security.authenticate + ).toHaveBeenCalledTimes(1); + }); + }); + describe('invalidateAsInternalUser()', () => { it('returns null when security feature is disabled', async () => { mockLicense.isEnabled.mockReturnValue(false); diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts index 0eef6fac74035..2f662554159eb 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts @@ -18,6 +18,7 @@ import { BasicHTTPAuthorizationHeaderCredentials, HTTPAuthorizationHeader, } from '../http_authentication'; +import { getFakeKibanaRequest } from './fake_kibana_request'; /** * Represents the options to create an APIKey class instance that will be @@ -139,6 +140,21 @@ export interface InvalidateAPIKeyResult { }>; } +/** + * Represents the parameters for validating API Key credentials. + */ +export interface ValidateAPIKeyParams { + /** + * Unique id for this API key + */ + id: string; + + /** + * Generated API Key (secret) + */ + api_key: string; +} + /** * Class responsible for managing Elasticsearch API keys. */ @@ -335,6 +351,30 @@ export class APIKeys { return result; } + /** + * Tries to validate an API key. + * @param apiKeyPrams ValidateAPIKeyParams. + */ + async validate(apiKeyPrams: ValidateAPIKeyParams): Promise { + if (!this.license.isEnabled()) { + return false; + } + + const fakeRequest = getFakeKibanaRequest(apiKeyPrams); + + this.logger.debug(`Trying to validate an API key`); + + try { + await this.clusterClient.asScoped(fakeRequest).asCurrentUser.security.authenticate(); + this.logger.debug(`API key was validated successfully`); + return true; + } catch (e) { + this.logger.info(`Failed to validate API key: ${e.message}`); + } + + return false; + } + private doesErrorIndicateAPIKeysAreDisabled(e: Record) { const disabledFeature = e.body?.error?.['disabled.feature']; return disabledFeature === 'api_keys'; diff --git a/x-pack/plugins/security/server/authentication/api_keys/fake_kibana_request.ts b/x-pack/plugins/security/server/authentication/api_keys/fake_kibana_request.ts new file mode 100644 index 0000000000000..a78accb416e63 --- /dev/null +++ b/x-pack/plugins/security/server/authentication/api_keys/fake_kibana_request.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Request } from '@hapi/hapi'; + +import { CoreKibanaRequest } from '@kbn/core/server'; + +export function getFakeKibanaRequest(apiKey: { id: string; api_key: string }) { + const requestHeaders: Record = {}; + + requestHeaders.authorization = `ApiKey ${Buffer.from(`${apiKey.id}:${apiKey.api_key}`).toString( + 'base64' + )}`; + + return CoreKibanaRequest.from({ + headers: requestHeaders, + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + } as unknown as Request); +} diff --git a/x-pack/plugins/security/server/authentication/authentication_service.ts b/x-pack/plugins/security/server/authentication/authentication_service.ts index c22ac5fceecb6..d0ffb92bf6c47 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.ts @@ -63,6 +63,7 @@ export interface InternalAuthenticationServiceStart extends AuthenticationServic | 'areAPIKeysEnabled' | 'create' | 'invalidate' + | 'validate' | 'grantAsInternalUser' | 'invalidateAsInternalUser' >; @@ -81,6 +82,7 @@ export interface AuthenticationServiceStart { | 'areAPIKeysEnabled' | 'create' | 'invalidate' + | 'validate' | 'grantAsInternalUser' | 'invalidateAsInternalUser' >; @@ -354,6 +356,7 @@ export class AuthenticationService { create: apiKeys.create.bind(apiKeys), grantAsInternalUser: apiKeys.grantAsInternalUser.bind(apiKeys), invalidate: apiKeys.invalidate.bind(apiKeys), + validate: apiKeys.validate.bind(apiKeys), invalidateAsInternalUser: apiKeys.invalidateAsInternalUser.bind(apiKeys), }, 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 ceb0dab8a7f70..f46908902376f 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 19dcd2d3a38cb..7ea90990a76e5 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/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 10e3b99484f52..0871db0bbde72 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -141,6 +141,7 @@ describe('Security Plugin', () => { "grantAsInternalUser": [Function], "invalidate": [Function], "invalidateAsInternalUser": [Function], + "validate": [Function], }, "getCurrentUser": [Function], }, diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 1566723f9c6c5..aec2bc40a4824 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -56,6 +56,7 @@ export const SCROLLING_DISABLED_CLASS_NAME = 'scrolling-disabled' as const; export const FULL_SCREEN_TOGGLED_CLASS_NAME = 'fullScreenToggled' as const; export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51' as const; export const ENDPOINT_METADATA_INDEX = 'metrics-endpoint.metadata-*' as const; +export const ENDPOINT_METRICS_INDEX = '.ds-metrics-endpoint.metrics-*' as const; export const DEFAULT_RULE_REFRESH_INTERVAL_ON = true as const; export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000 as const; // ms export const DEFAULT_RULE_NOTIFICATION_QUERY_SIZE = 100 as const; 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 99f5413e6688b..3cee4c3dbe384 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 c09a2c27ea576..d595dc88441cc 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 0000000000000..710c0b55a86f9 --- /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 cf1266b1b9a71..b63c27f71f79a 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 0a99da6b4f6f3..86018ff1a7b88 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 9985ef4102736..5d35811368b39 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 0000000000000..4edb5c6885af1 --- /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 0000000000000..3bf66b5e31ec6 --- /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 a1a0a9079234a..93436ffa52d6b 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 706283288f58c..b5923bb4f3b36 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 d08f6b05432d6..5c2d6d2a6af26 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/e2e/dashboards/enable_risk_score.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/dashboards/enable_risk_score.cy.ts index 94cf890ba5dd8..bb30a0f40a45e 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 bcbc85849c166..1a6c5f294caba 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/hosts/host_risk_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/hosts/host_risk_tab.cy.ts index c7e84d6824561..cf35a51e49865 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 c20a4ae39026d..a6691225808ef 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 5452d59b68c07..32c6a336b9096 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/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 529847261e06d..59c8d6a4103f7 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 2bc4badf3cfa1..836eae6076f17 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 4429bfae8820a..4e6edd139ec8f 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 8cdc0bbf2c1e9..3e96bbcd2cb2c 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/exceptions_table.ts b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts index 482ea64cf4a8b..c4ceae7032ac0 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 4b81e4d728990..a4da5eb67022a 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 d3f29323b5268..2efedeb71836e 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 61ee6ff430c9b..0e510e1247e19 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 c1e654d530653..51fd3156fe515 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 1ff04833db2a4..76a512a6fcb88 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 a0ef37259aaa2..5ec3b5e158138 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 820488225e17b..3c095c5ed87da 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 5848303957027..bb780af23c82c 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 0000000000000..4adb83b26cdda --- /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 494ecb0c6b4d0..8a76223bb290c 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 0000000000000..081ab17aa0dff --- /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 0000000000000..8542c445b5d14 --- /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 0000000000000..5c5de496b04b5 --- /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 0000000000000..74942f0f4ad38 --- /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 0000000000000..e7836cd6cd3ad --- /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 3d046e349de31..d65405cd1c48f 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 6dcb93321056e..449e2adee8bf8 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 37e6a9b6ba0a6..26b23380f15cc 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 956275d43bac7..bd29838952bad 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 b654b01dbb4ef..60a4e73a6c5cf 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 f4fdb22aff136..1ddb1c6ea759e 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 0967c9c2533d3..d3b23bb1a04f4 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 cc721b4944244..7e0e08557de53 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 10f36a14e2c5e..442c7320e83b2 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 6686239e053f9..d01bc59c0bbe3 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 0aa6fa9c20875..38dbf65542254 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 40a00178c31b8..7c113670d2f3a 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 fa2b7f16ce9f7..d598969acd155 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 ac22095820255..f28d6b53821e2 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 0000000000000..cd8be2d925c3f --- /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 cdeec1aeb4adb..1b28952acd7d8 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 74c2471b00f7e..68e7c0030bc2a 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 0000000000000..9090eba69b009 --- /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 163402ac7bfd7..fa063dbc98242 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 185ca7040fe21..62af07af3ea24 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 7d69b8ee52122..aaa2a7ace106a 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 b35e1d9b0f89c..fcaad7017ca47 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 cf9711c58f8cb..90405c022799d 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 aeba71acb6ab8..a2206c9c562e7 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 92879c56e9885..d22bff896eb0b 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 acd2040ad3e27..ed7f761bec143 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 0000000000000..579167c73f4cb --- /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 0000000000000..d0df6a7320015 --- /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 816c2963779f9..0276d89ae1449 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 1281e67a49b71..f5345f42f810b 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 5c5554b72ad8a..1da78d8d97a29 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 9cbe2362acb6d..8201863171227 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 f6dfc40caa69a..e437ad1120c04 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 0000000000000..4f34bd2dc03ce --- /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 82934d0cccac2..1c7433c9caabd 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 216e202052ee7..0452644b12d00 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 81ebc4b24cf9c..680249fb8f89f 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 5c04e749e481d..e7cc2967ba62d 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/index.ts b/x-pack/plugins/security_solution/public/exceptions/api/index.ts index cda467ea62924..72460c8bb3a2b 100644 --- a/x-pack/plugins/security_solution/public/exceptions/api/index.ts +++ b/x-pack/plugins/security_solution/public/exceptions/api/index.ts @@ -5,3 +5,4 @@ * 2.0. */ export * from './exception_api'; +export * from './list_api'; 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 new file mode 100644 index 0000000000000..ba7a24bb44285 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/api/list_api.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 { fetchExceptionLists, updateExceptionList } from '@kbn/securitysolution-list-api'; + +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, + LinkListToRules, + UnlinkListFromRules, + UpdateExceptionList, +} from './types'; +import { fetchRules, patchRule } from '../../detection_engine/rule_management/api/api'; +import type { Rule } from '../../detection_engine/rule_management/logic'; + +export const getListById = async ({ id, http }: FetchListById) => { + try { + const abortCtrl = new AbortController(); + const filters = getFilters({ + filters: { list_id: id }, + namespaceTypes: ['single', 'agnostic'], + hideLists: ALL_ENDPOINT_ARTIFACT_LIST_IDS, + }); + const namespaceTypes = ['single', 'agnostic'].join(); + + const { data } = await fetchExceptionLists({ + filters, + http: http as HttpSetup, + signal: abortCtrl.signal, + namespaceTypes, + pagination: {}, + }); + abortCtrl.abort(); + + if (data && data.length) return data[0]; + return null; + } catch (error) { + throw new Error(error); + } +}; +export const getListRules = async (listId: string) => { + try { + const abortCtrl = new AbortController(); + const { data: rules } = await fetchRules({ + signal: abortCtrl.signal, + }); + abortCtrl.abort(); + return rules.reduce((acc: Rule[], rule, index) => { + const listExceptions = rule.exceptions_list?.find( + (exceptionList) => exceptionList.list_id === listId + ); + if (listExceptions) acc.push(rule); + return acc; + }, []); + } catch (error) { + throw new Error(error); + } +}; + +export const updateList = async ({ list, http }: UpdateExceptionList) => { + try { + const abortCtrl = new AbortController(); + await updateExceptionList({ + http: http as HttpSetup, + list, + signal: abortCtrl.signal, + }); + abortCtrl.abort(); + } catch (error) { + throw new Error(error); + } +}; + +export const unlinkListFromRules = async ({ rules, listId }: UnlinkListFromRules) => { + try { + if (!rules.length) return; + const abortCtrl = new AbortController(); + await asyncForEach(rules, async (rule) => { + const exceptionLists: ListArray | [] = (rule.exceptions_list ?? []).filter( + ({ list_id: id }) => id !== listId + ); + await patchRule({ + ruleProperties: { + rule_id: rule.rule_id, + exceptions_list: exceptionLists, + }, + signal: abortCtrl.signal, + }); + }); + } catch (error) { + throw new Error(error); + } +}; + +export const linkListToRules = async ({ + rules, + listId, + id, + listType, + listNamespaceType, +}: LinkListToRules) => { + try { + if (!rules.length) return; + const abortCtrl = new AbortController(); + await asyncForEach(rules, async (rule) => { + const newExceptionList: List = { + list_id: listId, + id, + type: listType, + namespace_type: listNamespaceType, + }; + const exceptionLists: ListArray | [] = [...(rule.exceptions_list ?? []), newExceptionList]; + await patchRule({ + ruleProperties: { + rule_id: rule.rule_id, + exceptions_list: exceptionLists, + }, + signal: abortCtrl.signal, + }); + }); + } catch (error) { + throw new Error(error); + } +}; diff --git a/x-pack/plugins/security_solution/public/exceptions/api/types.ts b/x-pack/plugins/security_solution/public/exceptions/api/types.ts index 51a2c4266a59a..f75204cf8e466 100644 --- a/x-pack/plugins/security_solution/public/exceptions/api/types.ts +++ b/x-pack/plugins/security_solution/public/exceptions/api/types.ts @@ -7,11 +7,14 @@ import type { CreateExceptionListItemSchema, + ExceptionListType, NamespaceType, UpdateExceptionListItemSchema, + UpdateExceptionListSchema, } from '@kbn/securitysolution-io-ts-list-types'; import type { Pagination } from '@elastic/eui'; import type { HttpSetup } from '@kbn/core-http-browser'; +import type { Rule } from '@kbn/securitysolution-exception-list-components'; export interface FetchItems { http: HttpSetup | undefined; @@ -21,6 +24,11 @@ export interface FetchItems { search?: string; filter?: string; } + +export interface FetchListById { + http: HttpSetup | undefined; + id: string; +} export interface DeleteExceptionItem { id: string; namespaceType: NamespaceType; @@ -35,3 +43,34 @@ export interface AddExceptionItem { http: HttpSetup | undefined; exception: CreateExceptionListItemSchema; } + +export interface UpdateExceptionList { + http: HttpSetup | undefined; + list: UpdateExceptionListSchema; +} + +export interface ExportExceptionList { + id: string; + http: HttpSetup | undefined; + listId: string; + namespaceType: NamespaceType; +} + +export interface DeleteExceptionList { + id: string; + http: HttpSetup | undefined; + namespaceType: NamespaceType; +} + +export interface LinkListToRules { + rules: Rule[]; + listId: string; + id: string; + listType: ExceptionListType; + listNamespaceType: NamespaceType; +} + +export interface UnlinkListFromRules { + rules: Rule[]; + listId: string; +} diff --git a/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx index 5ff6b38484cf4..8031de23ef4c4 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/exceptions_list_card/index.tsx @@ -30,8 +30,6 @@ import { TitleBadge } from '../title_badge'; import * as i18n from '../../translations'; import { ListExceptionItems } from '../list_exception_items'; import { useExceptionsListCard } from '../../hooks/use_exceptions_list.card'; -import { useGetSecuritySolutionLinkProps } from '../../../common/components/links'; -import { SecurityPageName } from '../../../../common/constants'; interface ExceptionsListCardProps { exceptionsList: ExceptionListInfo; @@ -98,18 +96,13 @@ export const ExceptionsListCard = memo( onAddExceptionClick, handleConfirmExceptionFlyout, handleCancelExceptionItemFlyout, + goToExceptionDetail, } = useExceptionsListCard({ exceptionsList, handleExport, handleDelete, }); - // routes to x-pack/plugins/security_solution/public/exceptions/routes.tsx - const { onClick: goToExceptionDetail } = useGetSecuritySolutionLinkProps()({ - deepLinkId: SecurityPageName.sharedExceptionListDetails, - path: `/exceptions/shared/${exceptionsList.list_id}`, - }); - return ( diff --git a/x-pack/plugins/security_solution/public/exceptions/components/exceptions_utility/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/exceptions_utility/index.tsx index 029c92e371fc9..bb6657d44bfa3 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/exceptions_utility/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/exceptions_utility/index.tsx @@ -32,6 +32,7 @@ const MyUtilities = styled(EuiFlexGroup)` const StyledCondition = styled.span` display: inline-block !important; vertical-align: middle !important; + line-height: 1; `; interface ExceptionsUtilityComponentProps { dataTestSubj?: string; diff --git a/x-pack/plugins/security_solution/public/exceptions/components/index.ts b/x-pack/plugins/security_solution/public/exceptions/components/index.ts new file mode 100644 index 0000000000000..8de30af731151 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/components/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './create_shared_exception_list'; +export * from './exceptions_list_card'; +export * from './exceptions_utility'; +export * from './import_exceptions_list_flyout'; +export * from './list_details_link_anchor'; +export * from './list_exception_items'; +export * from './list_with_search'; +export * from './manage_rules'; +export * from './shared_list_utilty_bar'; +export * from './title_badge'; +export * from './list_search_bar'; diff --git a/x-pack/plugins/security_solution/public/exceptions/components/list_search_bar/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/list_search_bar/index.tsx index dcc559e4f62ef..6cd397c75de4a 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/list_search_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/list_search_bar/index.tsx @@ -15,6 +15,7 @@ interface ExceptionListsTableSearchProps { onSearch: (args: Parameters>[0]) => void; } +// TODO replace this component with the @Kbn/securitysolution-exception-list-components export const EXCEPTIONS_SEARCH_SCHEMA = { strict: true, fields: { @@ -36,7 +37,7 @@ export const EXCEPTIONS_SEARCH_SCHEMA = { }, }; -export const ExceptionsSearchBar = React.memo(({ onSearch }) => { +export const ListsSearchBar = React.memo(({ onSearch }) => { return ( (({ ); }); -ExceptionsSearchBar.displayName = 'ExceptionsSearchBar'; +ListsSearchBar.displayName = 'ListsSearchBar'; 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 new file mode 100644 index 0000000000000..9deda24248fc8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/components/list_with_search/index.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { FC } from 'react'; +import { EuiPanel } from '@elastic/eui'; + +import type { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; + +import { + SearchBar, + EmptyViewerState, + ViewerStatus, +} from '@kbn/securitysolution-exception-list-components'; +import { AddExceptionFlyout } from '../../../detection_engine/rule_exceptions/components/add_exception_flyout'; +import { EditExceptionFlyout } from '../../../detection_engine/rule_exceptions/components/edit_exception_flyout'; +import * as i18n from '../../translations'; +import { useListWithSearchComponent } from '../../hooks/use_list_with_search'; +import { ListExceptionItems } from '..'; + +interface ListWithSearchComponentProps { + list: ExceptionListSchema; + isReadOnly: boolean; + refreshExceptions?: boolean; +} + +const ListWithSearchComponent: FC = ({ + list, + isReadOnly, + refreshExceptions, +}) => { + const { + listName, + exceptions, + listType, + lastUpdated, + pagination, + emptyViewerTitle, + emptyViewerBody, + viewerStatus, + ruleReferences, + showAddExceptionFlyout, + showEditExceptionFlyout, + exceptionToEdit, + exceptionViewerStatus, + onSearch, + onAddExceptionClick, + onDeleteException, + onEditExceptionItem, + onPaginationChange, + handleCancelExceptionItemFlyout, + handleConfirmExceptionFlyout, + } = useListWithSearchComponent(list, refreshExceptions); + return ( + <> + {showAddExceptionFlyout ? ( + + ) : viewerStatus === ViewerStatus.EMPTY || viewerStatus === ViewerStatus.LOADING ? ( + + ) : ( + + <> + {showEditExceptionFlyout && exceptionToEdit && ( + + )} + + + + + )} + + ); +}; + +ListWithSearchComponent.displayName = 'ListWithSearchComponent'; + +export const ListWithSearch = React.memo(ListWithSearchComponent); + +ListWithSearch.displayName = 'ListWithSearch'; 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 new file mode 100644 index 0000000000000..f4254123999c1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/components/manage_rules/index.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { FC } from 'react'; +import React, { memo } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiSpacer, + EuiText, + EuiTitle, + useGeneratedHtmlId, +} from '@elastic/eui'; +import type { Rule } from '../../../detection_engine/rule_management/logic/types'; +import { ExceptionsAddToRulesTable } from '../../../detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table'; +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, + saveIsDisabled = true, + onSave, + onCancel, + onRuleSelectionChange, + }) => { + const complicatedFlyoutTitleId = useGeneratedHtmlId({ + prefix: 'complicatedFlyoutTitle', + }); + return ( + + + +

{i18n.MANAGE_RULES_HEADER}

+
+ + + {i18n.MANAGE_RULES_DESCRIPTION} + +
+ + + + + + + + {i18n.MANAGE_RULES_CANCEL} + + + + + {i18n.MANAGE_RULES_SAVE} + + + + +
+ ); + } +); +ManageRules.displayName = 'ManageRules'; 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 e4c3653a2693a..5d1cdf3441a48 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/index.ts b/x-pack/plugins/security_solution/public/exceptions/hooks/index.ts new file mode 100644 index 0000000000000..ecea322a1e0b2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export * from './use_all_exception_lists'; +export * from './use_create_shared_list'; +export * from './use_exceptions_list.card'; +export * from './use_import_exception_list'; +export * from './use_list_exception_items'; +export * from './use_list_with_search'; +export * from './use_list_detail_view'; diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx b/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx index 882b94772ab71..bdef9b8dd4e22 100644 --- a/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_exceptions_list.card/index.tsx @@ -12,9 +12,11 @@ import type { } from '@kbn/securitysolution-io-ts-list-types'; import { ViewerStatus } from '@kbn/securitysolution-exception-list-components'; import { useGeneratedHtmlId } from '@elastic/eui'; +import { useGetSecuritySolutionLinkProps } from '../../../common/components/links'; +import { SecurityPageName } from '../../../../common/constants'; import type { ExceptionListInfo } from '../use_all_exception_lists'; import { useListExceptionItems } from '../use_list_exception_items'; -import * as i18n from '../../translations/list_details'; +import * as i18n from '../../translations'; import { checkIfListCannotBeEdited } from '../../utils/list.utils'; interface ListAction { @@ -141,6 +143,11 @@ export const useExceptionsListCard = ({ [fetchItems, setShowAddExceptionFlyout, setShowEditExceptionFlyout] ); + // routes to x-pack/plugins/security_solution/public/exceptions/routes.tsx + const { onClick: goToExceptionDetail } = useGetSecuritySolutionLinkProps()({ + deepLinkId: SecurityPageName.sharedExceptionListDetails, + path: `/exceptions/shared/${exceptionsList.list_id}`, + }); return { listId, listName, @@ -169,5 +176,6 @@ export const useExceptionsListCard = ({ onAddExceptionClick, handleConfirmExceptionFlyout, handleCancelExceptionItemFlyout, + goToExceptionDetail, }; }; 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 new file mode 100644 index 0000000000000..c885cabda32f0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_detail_view/index.ts @@ -0,0 +1,357 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, useEffect, useMemo, useState } from 'react'; +import type { + BackOptions, + ListDetails, + Rule as UIRule, +} from '@kbn/securitysolution-exception-list-components'; +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'; +import { useKibana, useToasts } from '../../../common/lib/kibana'; +import { + updateList, + getListRules, + getListById, + unlinkListFromRules, + linkListToRules, +} from '../../api'; +import { checkIfListCannotBeEdited, isAnExceptionListItem } from '../../utils/list.utils'; +import * as i18n from '../../translations'; + +interface ReferenceModalState { + contentText: string; + rulesReferences: string[]; + isLoading: boolean; + listId: string; + listNamespaceType: NamespaceType; +} + +const exceptionReferenceModalInitialState: ReferenceModalState = { + contentText: '', + rulesReferences: [], + isLoading: false, + listId: '', + listNamespaceType: 'single', +}; + +export const useExceptionListDetails = () => { + const toasts = useToasts(); + const { services } = useKibana(); + const { http, notifications } = services; + const { navigateToApp } = services.application; + + const { exportExceptionList, deleteExceptionList } = useApi(http); + + const { exceptionListId } = useParams<{ + exceptionListId: string; + }>(); + + const [{ loading: userInfoLoading, canUserCRUD, canUserREAD }] = useUserData(); + + const [isLoading, setIsLoading] = useState(); + const [showManageButtonLoader, setShowManageButtonLoader] = useState(false); + const [list, setList] = useState(); + const [invalidListId, setInvalidListId] = useState(false); + const [linkedRules, setLinkedRules] = useState([]); + const [newLinkedRules, setNewLinkedRules] = useState([]); + const [canUserEditList, setCanUserEditList] = useState(true); + const [viewerStatus, setViewerStatus] = useState(''); + const [exportedList, setExportedList] = useState(); + const [showReferenceErrorModal, setShowReferenceErrorModal] = useState(false); + const [referenceModalState, setReferenceModalState] = useState( + exceptionReferenceModalInitialState + ); + const [disableManageButton, setDisableManageButton] = useState(true); + const [refreshExceptions, setRefreshExceptions] = useState(false); + + const headerBackOptions: BackOptions = useMemo( + () => ({ + pageId: SecurityPageName.exceptions, + path: '', + onNavigate: () => { + navigateToApp(APP_UI_ID, { + deepLinkId: SecurityPageName.exceptions, + path: '', + }); + }, + }), + [navigateToApp] + ); + + const handleErrorStatus = useCallback( + (error: Error, errorTitle?: string, errorDescription?: string) => { + toasts?.addError(error, { + title: errorTitle || i18n.EXCEPTION_ERROR_TITLE, + toastMessage: errorDescription || i18n.EXCEPTION_ERROR_DESCRIPTION, + }); + setViewerStatus(ViewerStatus.ERROR); + }, + [toasts] + ); + + const initializeListRules = useCallback(async (result) => { + const listRules = await getListRules(result.list_id); + setLinkedRules(listRules); + }, []); + + const initializeList = useCallback(async () => { + try { + if (ALL_ENDPOINT_ARTIFACT_LIST_IDS.includes(exceptionListId)) return setInvalidListId(true); + setIsLoading(true); + + const result = await getListById({ + id: exceptionListId, + http, + }); + if (!result || !isAnExceptionListItem(result)) return setInvalidListId(true); + + setList(result); + await initializeListRules(result); + setIsLoading(false); + setInvalidListId(false); + if (checkIfListCannotBeEdited(result)) return setCanUserEditList(false); + } catch (error) { + handleErrorStatus(error); + } + }, [exceptionListId, http, initializeListRules, handleErrorStatus]); + + useEffect(() => { + initializeList(); + }, [initializeList]); + + const [showManageRulesFlyout, setShowManageRulesFlyout] = useState(false); + + const onEditListDetails = useCallback( + async (listDetails: ListDetails) => { + try { + if (list) + await updateList({ + http, + list: { + id: list.id, + list_id: exceptionListId, + type: list.type, + name: listDetails.name, + description: listDetails.description || list.description, + }, + }); + } catch (error) { + handleErrorStatus(error); + } + }, + [exceptionListId, handleErrorStatus, http, list] + ); + const onExportList = useCallback(async () => { + try { + if (!list) return; + await exportExceptionList({ + id: list.id, + listId: list.list_id, + namespaceType: list.namespace_type, + onError: (error: Error) => handleErrorStatus(error), + onSuccess: (blob) => { + setExportedList(blob); + toasts?.addSuccess(i18n.EXCEPTION_LIST_EXPORTED_SUCCESSFULLY(list.list_id)); + }, + }); + } catch (error) { + handleErrorStatus(error); + } + }, [list, exportExceptionList, handleErrorStatus, toasts]); + + // #region DeleteList + + const handleDeleteSuccess = useCallback( + (listId?: string) => () => { + notifications.toasts.addSuccess({ + title: i18n.exceptionDeleteSuccessMessage(listId ?? referenceModalState.listId), + }); + }, + [notifications.toasts, referenceModalState.listId] + ); + + const handleDeleteError = useCallback( + (err: Error & { body?: { message: string } }): void => { + handleErrorStatus(err); + }, + [handleErrorStatus] + ); + const onDeleteList = useCallback(async () => { + try { + if (!list) return; + + await deleteExceptionList({ + id: list.id, + namespaceType: list.namespace_type, + onError: handleDeleteError, + onSuccess: handleDeleteSuccess, + }); + } catch (error) { + handleErrorStatus(error); + } finally { + setReferenceModalState(exceptionReferenceModalInitialState); + setShowReferenceErrorModal(false); + navigateToApp(APP_UI_ID, { + deepLinkId: SecurityPageName.exceptions, + path: '', + }); + } + }, [ + list, + deleteExceptionList, + handleDeleteError, + handleDeleteSuccess, + handleErrorStatus, + navigateToApp, + ]); + + const handleDelete = useCallback(() => { + try { + if (!list) return; + setReferenceModalState({ + contentText: linkedRules.length + ? i18n.referenceErrorMessage(linkedRules.length) + : i18n.defaultDeleteListMessage(list.name), + rulesReferences: linkedRules.map(({ name }) => name), + isLoading: true, + listId: list.list_id, + listNamespaceType: list.namespace_type, + }); + setShowReferenceErrorModal(true); + } catch (error) { + handleErrorStatus(error); + } + }, [handleErrorStatus, linkedRules, list]); + + const handleCloseReferenceErrorModal = useCallback((): void => { + setShowReferenceErrorModal(false); + setReferenceModalState({ + contentText: '', + rulesReferences: [], + isLoading: false, + listId: '', + listNamespaceType: 'single', + }); + }, []); + const handleReferenceDelete = useCallback(async (): Promise => { + try { + await unlinkListFromRules({ rules: linkedRules, listId: exceptionListId }); + onDeleteList(); + } catch (err) { + handleErrorStatus(err); + } + }, [exceptionListId, linkedRules, handleErrorStatus, onDeleteList]); + + // #endregion + + // #region Manage Rules + + const resetManageRulesAfterSaving = useCallback(() => { + setLinkedRules(newLinkedRules); + setNewLinkedRules(newLinkedRules); + setShowManageRulesFlyout(false); + setShowManageButtonLoader(false); + setDisableManageButton(true); + }, [newLinkedRules]); + const onManageRules = useCallback(() => { + setShowManageRulesFlyout(true); + }, []); + + const getRulesToAdd = useCallback(() => { + return newLinkedRules.filter((rule) => !linkedRules.includes(rule)); + }, [linkedRules, newLinkedRules]); + + const getRulesToRemove = useCallback(() => { + return linkedRules.filter((rule) => !newLinkedRules.includes(rule)); + }, [linkedRules, newLinkedRules]); + + const onRuleSelectionChange = useCallback((value) => { + setNewLinkedRules(value); + setDisableManageButton(false); + }, []); + + const onSaveManageRules = useCallback(async () => { + try { + if (!list) return setShowManageRulesFlyout(false); + + setShowManageButtonLoader(true); + const rulesToAdd = getRulesToAdd(); + const rulesToRemove = getRulesToRemove(); + + if ((!rulesToAdd.length && !rulesToRemove.length) || isEqual(rulesToAdd, rulesToRemove)) + return resetManageRulesAfterSaving(); + + Promise.all([ + unlinkListFromRules({ rules: rulesToRemove, listId: exceptionListId }), + linkListToRules({ + rules: rulesToAdd, + listId: exceptionListId, + id: list.id, + listType: list.type, + listNamespaceType: list.namespace_type, + }), + ]) + .then(() => { + setRefreshExceptions(true); + resetManageRulesAfterSaving(); + }) + .then(() => setRefreshExceptions(false)); + } catch (err) { + handleErrorStatus(err); + } + }, [ + list, + getRulesToAdd, + getRulesToRemove, + exceptionListId, + resetManageRulesAfterSaving, + handleErrorStatus, + ]); + const onCancelManageRules = useCallback(() => { + setShowManageRulesFlyout(false); + }, []); + + // #endregion + + return { + isLoading: isLoading || userInfoLoading, + invalidListId, + isReadOnly: !!(!canUserCRUD && canUserREAD), + list, + listName: list?.name, + listDescription: list?.description, + listId: exceptionListId, + canUserEditList, + linkedRules, + exportedList, + viewerStatus, + showManageRulesFlyout, + headerBackOptions, + referenceModalState, + showReferenceErrorModal, + showManageButtonLoader, + refreshExceptions, + disableManageButton, + handleDelete, + onEditListDetails, + onExportList, + onDeleteList, + onManageRules, + onSaveManageRules, + onCancelManageRules, + onRuleSelectionChange, + handleCloseReferenceErrorModal, + handleReferenceDelete, + }; +}; 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 0da8169baf2f4..ad1db5e82585c 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 new file mode 100644 index 0000000000000..14a51deedb493 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_list_with_search/index.ts @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, useEffect, useMemo, useState } from 'react'; + +import type { + ExceptionListItemSchema, + ExceptionListSchema, +} from '@kbn/securitysolution-io-ts-list-types'; + +import type { GetExceptionItemProps } from '@kbn/securitysolution-exception-list-components'; +import { ViewerStatus } from '@kbn/securitysolution-exception-list-components'; + +import * as i18n from '../../translations'; +import { useListExceptionItems } from '..'; + +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); + + const onFinishFetchingExceptions = useCallback(() => { + setViewerStatus(''); + }, [setViewerStatus]); + + const onEditExceptionItem = (exception: ExceptionListItemSchema) => { + setExceptionToEdit(exception); + setShowEditExceptionFlyout(true); + }; + const { + exceptionViewerStatus, + exceptions, + lastUpdated, + pagination, + ruleReferences: exceptionListReferences, + fetchItems, + onDeleteException, + onPaginationChange, + } = useListExceptionItems({ + list, + deleteToastTitle: i18n.EXCEPTION_ITEM_DELETE_TITLE, + deleteToastBody: (name) => i18n.EXCEPTION_ITEM_DELETE_TEXT(name), + errorToastBody: i18n.EXCEPTION_ERROR_DESCRIPTION, + errorToastTitle: i18n.EXCEPTION_ERROR_TITLE, + onEditListExceptionItem: onEditExceptionItem, + onFinishFetchingExceptions, + }); + + useEffect(() => { + fetchItems(null, ViewerStatus.LOADING); + }, [fetchItems, refreshExceptions]); + + const emptyViewerTitle = useMemo(() => { + return viewerStatus === ViewerStatus.EMPTY ? i18n.EXCEPTION_LIST_EMPTY_VIEWER_TITLE : ''; + }, [viewerStatus]); + + const emptyViewerBody = useMemo(() => { + return viewerStatus === ViewerStatus.EMPTY + ? i18n.EXCEPTION_LIST_EMPTY_VIEWER_BODY(list.name) + : ''; + }, [list.name, viewerStatus]); + + // #region Callbacks + + const onSearch = useCallback( + async (options?: GetExceptionItemProps) => { + setViewerStatus(ViewerStatus.SEARCHING); + fetchItems(options, ViewerStatus.EMPTY_SEARCH); + }, + [fetchItems, setViewerStatus] + ); + + const onAddExceptionClick = useCallback(() => { + setShowAddExceptionFlyout(true); + fetchItems(); + }, [fetchItems, setShowAddExceptionFlyout]); + + const handleCancelExceptionItemFlyout = () => { + setShowAddExceptionFlyout(false); + setShowEditExceptionFlyout(false); + }; + const handleConfirmExceptionFlyout = useCallback( + (didExceptionChange: boolean): void => { + setShowAddExceptionFlyout(false); + setShowEditExceptionFlyout(false); + if (!didExceptionChange) return; + fetchItems(); + }, + [fetchItems, setShowAddExceptionFlyout, setShowEditExceptionFlyout] + ); + // #endregion + + return { + exceptionViewerStatus, + listName: list.name, + exceptions, + listType: list.type, + lastUpdated, + pagination, + viewerStatus, + emptyViewerTitle, + emptyViewerBody, + ruleReferences: exceptionListReferences, + showAddExceptionFlyout, + showEditExceptionFlyout, + exceptionToEdit, + onSearch, + onAddExceptionClick, + onDeleteException, + onEditExceptionItem, + onPaginationChange, + handleCancelExceptionItemFlyout, + handleConfirmExceptionFlyout, + }; +}; diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/index.ts b/x-pack/plugins/security_solution/public/exceptions/pages/index.ts new file mode 100644 index 0000000000000..4d0fb3e863549 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/pages/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 './shared_lists'; +export * from './list_detail_view'; 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 new file mode 100644 index 0000000000000..32544e23f5cb3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/pages/list_detail_view/index.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import type { FC } from 'react'; + +import { + EmptyViewerState, + ExceptionListHeader, + ViewerStatus, +} from '@kbn/securitysolution-exception-list-components'; +import { EuiLoadingContent } from '@elastic/eui'; +import { ReferenceErrorModal } from '../../../detections/components/value_lists_management_flyout/reference_error_modal'; +import type { Rule } from '../../../detection_engine/rule_management/logic/types'; +import { MissingPrivilegesCallOut } from '../../../detections/components/callouts/missing_privileges_callout'; +import { NotFoundPage } from '../../../app/404'; +import { AutoDownload } from '../../../common/components/auto_download/auto_download'; +import { ListWithSearch, ManageRules, ListDetailsLinkAnchor } from '../../components'; +import { useExceptionListDetails } from '../../hooks'; +import * as i18n from '../../translations'; + +export const ListsDetailViewComponent: FC = () => { + const { + isLoading, + invalidListId, + isReadOnly, + list, + canUserEditList, + listId, + linkedRules, + exportedList, + viewerStatus, + listName, + listDescription, + showManageRulesFlyout, + headerBackOptions, + showReferenceErrorModal, + referenceModalState, + showManageButtonLoader, + refreshExceptions, + disableManageButton, + onEditListDetails, + onExportList, + onManageRules, + onSaveManageRules, + onCancelManageRules, + onRuleSelectionChange, + handleDelete, + handleCloseReferenceErrorModal, + handleReferenceDelete, + } = useExceptionListDetails(); + + if (viewerStatus === ViewerStatus.ERROR) + return ; + + if (isLoading) return ; + + if (invalidListId || !listName || !listDescription || !list) return ; + return ( + <> + + + + + + + {showManageRulesFlyout ? ( + + ) : null} + + ); +}; + +ListsDetailViewComponent.displayName = 'ListsDetailViewComponent'; +export const ListsDetailView = React.memo(ListsDetailViewComponent); +ListsDetailView.displayName = 'ListsDetailView'; diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/detail_view.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/detail_view.tsx deleted file mode 100644 index 3a438dcecd048..0000000000000 --- a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/detail_view.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { memo } from 'react'; -import { useParams } from 'react-router-dom'; -import { EuiTitle } from '@elastic/eui'; -import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../common/endpoint/service/artifacts/constants'; -import { NotFoundPage } from '../../../app/404'; - -export const ExceptionListsDetailView = memo(() => { - const { exceptionListId: listId } = useParams<{ - exceptionListId: string; - }>(); - return ALL_ENDPOINT_ARTIFACT_LIST_IDS.includes(listId) ? ( - - ) : ( - -

{listId}

-
- ); -}); - -ExceptionListsDetailView.displayName = 'ExceptionListsDetailView'; 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 d2d1ecf20ad36..9cc6fd15b7ad7 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 @@ -34,20 +34,22 @@ import { useKibana } from '../../../common/lib/kibana'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import * as i18n from '../../translations/shared_list'; -import { ExceptionsTableUtilityBar } from '../../components/shared_list_utilty_bar'; +import { + ExceptionsTableUtilityBar, + ListsSearchBar, + ExceptionsListCard, + ImportExceptionListFlyout, + CreateSharedListFlyout, +} from '../../components'; import { useAllExceptionLists } from '../../hooks/use_all_exception_lists'; import { ReferenceErrorModal } from '../../../detections/components/value_lists_management_flyout/reference_error_modal'; import { patchRule } from '../../../detection_engine/rule_management/api/api'; -import { ExceptionsSearchBar } from '../../components/list_search_bar'; + import { getSearchFilters } from '../../../detection_engine/rule_management_ui/components/rules_table/helpers'; import { useUserData } from '../../../detections/components/user_info'; import { useListsConfig } from '../../../detections/containers/detection_engine/lists/use_lists_config'; import { MissingPrivilegesCallOut } from '../../../detections/components/callouts/missing_privileges_callout'; import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../common/endpoint/service/artifacts/constants'; -import { ExceptionsListCard } from '../../components/exceptions_list_card'; - -import { ImportExceptionListFlyout } from '../../components/import_exceptions_list_flyout'; -import { CreateSharedListFlyout } from '../../components/create_shared_exception_list'; import { AddExceptionFlyout } from '../../../detection_engine/rule_exceptions/components/add_exception_flyout'; @@ -69,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(); @@ -87,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 ?? [], }); @@ -454,7 +472,7 @@ export const SharedLists = React.memo(() => { color="accent" /> )} - {!initLoading && } + {!initLoading && } {loadingTableInfo && !initLoading && !showReferenceErrorModal && ( @@ -468,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/routes.tsx b/x-pack/plugins/security_solution/public/exceptions/routes.tsx index 1516e48703dba..13cc29411f97c 100644 --- a/x-pack/plugins/security_solution/public/exceptions/routes.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/routes.tsx @@ -11,8 +11,8 @@ import { Route } from '@kbn/kibana-react-plugin/public'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import * as i18n from './translations'; import { EXCEPTIONS_PATH, SecurityPageName } from '../../common/constants'; -import { ExceptionListsDetailView } from './pages/shared_lists/detail_view'; -import { SharedLists } from './pages/shared_lists'; + +import { SharedLists, ListsDetailView } from './pages'; import { SpyRoute } from '../common/utils/route/spy_routes'; import { NotFoundPage } from '../app/404'; import { useReadonlyHeader } from '../use_readonly_header'; @@ -30,7 +30,7 @@ const ExceptionsRoutes = () => ( const ExceptionsListDetailRoute = () => ( - + diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/index.ts b/x-pack/plugins/security_solution/public/exceptions/translations/index.ts index 9aa34e16e6054..727f557b6ee76 100644 --- a/x-pack/plugins/security_solution/public/exceptions/translations/index.ts +++ b/x-pack/plugins/security_solution/public/exceptions/translations/index.ts @@ -4,6 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export * from './list_details'; +export * from './list_details_view'; export * from './list_exception_items'; export * from './shared_list'; diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/list_details.ts b/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts similarity index 96% rename from x-pack/plugins/security_solution/public/exceptions/translations/list_details.ts rename to x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts index 8c027a9c8102e..45600807749ff 100644 --- a/x-pack/plugins/security_solution/public/exceptions/translations/list_details.ts +++ b/x-pack/plugins/security_solution/public/exceptions/translations/list_details_view.ts @@ -107,7 +107,7 @@ export const MANAGE_RULES_SAVE = i18n.translate( export const MANAGE_RULES_HEADER = i18n.translate( 'xpack.securitySolution.exceptions.list.manage_rules_header', { - defaultMessage: 'Manege rules', + defaultMessage: 'Manage rules', } ); @@ -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 e545b782951c5..8e070f24f3353 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/exceptions/utils/list.utils.ts b/x-pack/plugins/security_solution/public/exceptions/utils/list.utils.ts index adc7cb012c6b4..1b7184f3576a3 100644 --- a/x-pack/plugins/security_solution/public/exceptions/utils/list.utils.ts +++ b/x-pack/plugins/security_solution/public/exceptions/utils/list.utils.ts @@ -5,9 +5,14 @@ * 2.0. */ +import type { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { exceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; + import { listIDsCannotBeEdited } from '../config'; -import type { ExceptionListInfo } from '../hooks/use_all_exception_lists'; -export const checkIfListCannotBeEdited = (list: ExceptionListInfo) => { +export const checkIfListCannotBeEdited = (list: ExceptionListSchema) => { return !!listIDsCannotBeEdited.find((id) => id === list.list_id); }; +export const isAnExceptionListItem = (list: ExceptionListSchema) => { + return exceptionListSchema.is(list); +}; 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 0000000000000..ef908b973dde2 --- /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 b7de3eba5ebe6..0fc7da65a40af 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 1711aee8b1932..3c95933e0bcbc 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 e1885881d81a5..979bd12b78c20 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 4aa8c1766c2f7..a18546a77fe27 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 25f070b6d14f7..5e451f6c44cc8 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 705375d48ec3e..24c9968c7cffa 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 4bc5b56b6fa3e..c5eb04940b799 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 f81334d7e84e4..09cad282168a2 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 c91dbb7129671..96e91946fc5a3 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 c6a623f19681f..df1e034a832f7 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 f19c2b2c70b88..cb46c3a255d3a 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 beceb9edbb7b5..ee5eebd109b05 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 0000000000000..ff9c524a76732 --- /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 0000000000000..0be933113e99c --- /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 0000000000000..9734e1c51de1f --- /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 0000000000000..6d841723bfdf8 --- /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 e890b724f78b5..1f4a50a46a62f 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 bcda8c7167bf6..c2ad001dde1a6 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 4a92bf323f2eb..8fd5dce2da0d2 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 38c27ba4a0d51..29b78e73f7b96 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 44d8ee8087ac5..562a69eeb9d0b 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 367a145774d7a..59967eda95cdd 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 6bee089027477..f91a9eed0d165 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 a7475bab09f14..12e78df1e49ec 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 32251df6318f2..0bff0f502175f 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 082ed270ada22..4a4c047d4cb1e 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 ea66ac153d3be..01c06946b5343 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 acc48bdc6c044..5af5d50dd6f46 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 b96fe3c0db0f9..204583c9eabaa 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 faafb8eca7eca..e490cd631f116 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 2af72c36d26ef..03bd1d84c9708 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 dcfd4337af803..5b26cff4f6d84 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 7e48b4b5c781a..951caa426e2c1 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 b78866ceb82ab..d37720f7218c2 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 17d7dc1a6438b..dfe598177bc55 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 eb3e9a2c280b3..d6423b3345835 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 5aff599670dc2..46ed0839a0e23 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 dc7ed0affa463..c2df23cd69c4f 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 9cb0e3d6e60bd..1760693b4187d 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 5324c02fcbfdf..55c8d2cad65bc 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 7ed71e3b6a4a8..39bd6044879b9 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 cfe0e860852b1..3f82606e5b6d6 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 8192c2a8e7f5c..54a90e89e52f5 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 c50ae488383f0..aaea29472bb1d 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 d891ad96a57c0..da1e111a8e5ff 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 0c69fba974b39..964c9c86ce87a 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 5a7899dd2dd3a..87cc01836aabf 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 051990db2549e..e2ed329ce42e6 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 43c95ff77503a..5ab4b6071c86f 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 49562601e3372..068c9df48db9a 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 89945ae91c6da..cd022f4a9507e 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 7898871bb9b3a..7dc51ab662073 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 a9af94cec09d3..0a4fe9394c547 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 c49f3cd7efefb..ecafc5f77b843 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 229755609cc38..d89ab690b166b 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 82e7f299ea8fa..86f93eb3446e2 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 854e7c05f1ee3..680b1d0d7ef3c 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 1e0e64288a25d..66bdd8611d63d 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 30103c4bf3c5c..f30c336db2616 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 fcdd974e6de15..ef99cd1102210 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 5117d4d48b9a0..bdf2553f55a87 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 49a04cb198d86..ad8cca6497114 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 c83f8cfc984ef..de2e199ca8381 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 d2bdcb9392ea8..725a6c26482a5 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 bda2c0ad53d27..c0d35a12070d5 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 5b8b2a5f1ecc8..b70c2bf7b823f 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 eb1af7292bdfa..6fefcf83d5ddc 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 219dacafba9d9..21dbe96ce7781 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 5fa244d380ae1..6b81462a7597e 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 f78eead09dea8..9156286051cdc 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 d5a13d5f0285f..3041946ef7853 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 9da4639332581..362863fb1bb8d 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 a5a2a5c4b5f66..2a38ef6156cb3 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 ae51a710c1136..269add22b580d 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 cb696cde2123f..88b82cdfcef87 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 1b6a0498a8f52..95f3cd9e92305 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 a971b81bd246f..f4824fb4b4f73 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 3f3febef5cc38..3752c6265d26d 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 6759145450ba6..5d3f424f0a786 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 d8e9a61e71b4e..864c199c4812e 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 0286e35f7d049..f37273c047e42 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 db1954bf32d48..087621759f73c 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 0000000000000..e345cca501947 --- /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 b3a48d5e8101b..22801fa281223 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 52adb1756ad50..bae3a8f8e9d51 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 48b117098e890..8d812354ba2ae 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 a94413ddbd695..1413e023cc05a 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 972c69b5052da..8cc79799763e8 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 ff068224efa5a..b65e1ed9488b3 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 2264c363bb04c..e7b617a2b14cb 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 5b39ffc36ed8e..50c20d1704322 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 5a74bfc9b1664..c5ba0b7f97ddd 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 4f816cefa327e..e4f439a2c1096 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 f4c3273953dca..271084ccfda2c 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 a07759a72e73f..47b80721be84c 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 21c0bd54b831c..566590bc6ffee 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 b6c0b9ceceb53..145e215b33358 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 30fcfcf0c8c86..2bba09ab05dc7 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 61b9caf627c65..165403e70f8b8 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 5ddaa9cdac60a..f184949f33a71 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 5013d8a5d731a..d6c580dd85bdd 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 4ce963325cd9e..948516a25043f 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 03e36246ac907..1da65f677d24c 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 a3a651443340c..b4865f1cf385b 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 f3131371bc359..239923e9cc33c 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 e01d287ef6b82..2bd98aeed43bb 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 d6ee39d46a212..33b762fee9beb 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 87db8907a1b33..671c5cc6e496d 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 bf988d5890bd7..efba66b2ebbf6 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 33af72a319c5b..d22d70bb3f47c 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 b41b3adf028e7..ce9f33670e5c1 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 5b3b8514b0236..55ff02621b110 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 a774917e65abe..43b78e3533df2 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 b4389ddf8c9bd..4115fa7f669fe 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 35765d1c9884c..3b4423397079b 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 49cc483694e29..bec7f128932a9 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 f70226e3a17fe..e5f59806e1a2b 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 c0b3af94f33d1..383841d89c623 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 430a1921553a8..193bdcb9119fb 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 1d50e5192b479..cea767e289764 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 c615e34089ee6..baf0396462f34 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 81d8782e6ce59..931551f8a8799 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 86cd009618c3a..1ac3123fd3fbf 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 295fb493e6ed2..af9033e7057fa 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 15a51b88c0975..7ad38aa9f345f 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 1a69f862b6954..1de3a586559cd 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 358419263a30e..2c657d681c563 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 0000000000000..ff73dc9d33df0 --- /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 686591b1738ab..ab990ab4a7875 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 48ae03aa6f70f..66d15946cd849 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 9f870734840dd..18f2a41384fbb 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 17b8dbcc08236..d589c216ea72d 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 ddffe659f0b41..2f20f27261bfe 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 c0317fc5939a6..27c5e63f7e7ad 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 e6b382e3e7db7..3a496167736d1 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 28b77bf1c18ef..2a19d7284c7dc 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 f555d11865315..0812ce63e357e 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 c5dab826da0f8..7d2fc7ef64a0f 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 f537c79c427e5..29979fd48a6ed 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 71090cbe65b64..d565b015ad9b4 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 d83fd0fff0226..e1d70d4760d2f 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 e8afca5d92c1f..1aff739a22cbc 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 2e44936da71b2..d1ee84ce33305 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 e9d8c440038b7..b613718047b81 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 a6397718b5953..3cb225fc8453c 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 f52c898b0b316..4adc26944dc65 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 217e400e8221a..45af4f583f286 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 aeaba22f4b281..d645fd560ee77 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 fa27159b49fd1..a0112215928b0 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 76d95e51f4692..58406e03f64e7 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 966b47558b7f5..b75b9f0123704 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 0000000000000..c5c7a81c2be88 --- /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 38779d72a19d4..50e3f7b9dd824 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 08d44e6911cae..3c8fece10bf16 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 cd5b4ebbdf9f2..08f7141ba51bb 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 e0bf3a485e835..8a9dae698ee1d 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 dbed80d94bef8..06051db044b0a 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 5e0d757a0c232..2bd143b5eb81f 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 57c608717970d..cce2c43be7f06 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 f27272de5cee0..999f9c5468e3e 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 f5b8a19959839..7ce38535022d3 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 785c950719b8f..7766b13dc2ca2 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 34d27330d7522..423b847017881 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 89818d4b601d2..b4818319f7bbb 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 d151424a662fb..03ed2491bcd6f 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 7cea863e990a2..2328a51a41112 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 736bb2a6d3c7a..b5b9c57570023 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 4a293f4b14442..f2a16f04b244f 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 b7d52e0c7960c..c22fd8c13730f 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 ce07c4a04bfca..328c49f6b22f8 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 2221085a9b518..92b534979f1e0 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 4b4176e8ebc6d..a770a6109037f 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 89f6364c7571c..60af2ecac4d6c 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 e525b02a6efd5..afba98240b949 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 394c8b047a372..bba60430286a4 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 a2562256f209b..5d203951f3cf4 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 0000000000000..10a563f0f74b9 --- /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 fbefe7152d9c0..c5587ebff2613 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 e8338519fe8f8..6017f4d2460d3 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 0b8153b2ac9d5..8ca50064b05d3 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 dcee904b4ceb1..4a5782fe5e07c 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 5e742ae4ed4bb..4913e95055e57 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 6b606117e1cae..f7abf2ee40ab8 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 3eab94d7a63e7..09dd786c19dc1 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 b1b2b87bc5d87..db43407a29b84 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 172c61f684c21..a1c4c8116f92d 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 53c7ae7cb3fe0..c3b95d43530ff 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 bbc808ea6e925..d77eab3654981 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 873ed4a120f19..665051435a8a4 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 2b55041c64fe7..bea8f042b1393 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 0c2c25e53bc31..d39b78f58a08d 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 73d7ed7c943bf..6e7cc80e971ac 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 a7ce614def97f..e93c0f1d9a633 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 0000000000000..a759fc2a22225 --- /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 790e19aaf182c..b4a6eca692666 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 c816403377b76..5a0bca5f20d4c 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 c6fdccd640393..3e5441528223d 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 7fc0083fc13c6..09a8f86170f5c 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 8204204bb34c0..269da717bb7a5 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 5d2cbf2aa963f..c3aa1d5f51a10 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 3ed1a0fb62f35..72d867ae33fac 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 77a7e0080caba..45787397342d6 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 ce6ef31fa4a10..a96ded43b09cb 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 9bc62b2b0ec53..f93b33390eae3 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 a91bdd8faea3e..1997c1fd721ae 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 dcda7721281e5..49f12a0b8f31a 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 1d985fb800a68..de15f958c8ae0 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 301ce56a51f5d..07cccf7dc8044 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 431397b640107..7e279f63d6f89 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 45144cc486895..64e5a58a731fc 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 18fe87682f81e..2add443b9b72c 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 be6ab836b1c0c..d8269fa351178 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 4d22e033ac124..a5c53099ae305 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 66147953d146d..c33a7fef7fa00 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 c0338b37b53e3..9ab3fdf00bae3 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 0d230518055f9..5e71faab05744 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 d455613c5e133..6dd54ff59c25c 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 23a7fc745bb68..f0e22bbbd04d2 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 ad5a36a1cb5e4..ff3561d8af15a 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 937f34263d382..9b8b1c1b9da5f 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 c3de284691700..c3fea13d48227 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 0c2e842434750..c760ee9025211 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 04da402d1caf9..0c57577bf6067 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 c7eb70ce22057..444e4ccbcc171 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 00d2a1c249cc8..40a9708e65c77 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 a9375cd4f50ff..5a19565ffa277 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 b477cd0148352..bdce467458c4f 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 35a9201a43fa5..7b0921a0d9bc2 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 ec9c831670716..75421c73dafd4 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 85997cb5399cd..3a8bbc3fa9a34 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 ace6bd3759783..38256544b51d1 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 4836132746057..56a519fc041af 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 3e5111b45a93d..2ae10b454a9cf 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 c69d5859d996b..111b56638f9c8 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 69130e81a668a..31e5786737b4d 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 0000000000000..7d8a95d7ad141 --- /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 764bd90d8d5ac..625cc38ba927f 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 04291353bfe13..833268fac1e0a 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 41d7c2e58474b..0000000000000 --- 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 b116edce296b6..9e36c14a5e90a 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 f8db0da46de0e..61f058989707d 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 689cca88265af..42797635419a5 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 a3da4f7e9ae89..d7a6baae8efa7 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 dcd1ad8e178dd..aa9352ec83cbd 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 365caf6dd1426..3c323e23e2954 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 3cde880bfcdd5..e7ce744e72437 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 fa217cf1cfd87..4b2eb8cc7f0a7 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 d19097ca6f91b..3e75a489518ba 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 67462232a22ba..edb7c2c699882 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 b82136e33acf2..a3f6b39cfa26c 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 517f9b0a16f58..701da673efcd6 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 9a13833e08437..6a658fe6e9dca 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 da19964e5b7ec..3e884113f2c84 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 0000000000000..496a91ba55963 --- /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 8637236f654d2..a36746623dbcd 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 5f15a2ed81d1f..f5ee4fc8ae35d 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 0242f17509a99..02b83342cd846 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 82d6ee6b1c4b2..e15c88ccf3aa1 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 3c8ca41801303..173e1a5f5b906 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 8d920ef4ba652..f90ce4a33b573 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 01bc55bf12467..5dbc62c86c417 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 04cec44000f78..3dcc8b0389cc9 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 0e64cc3788a12..1be28852dadeb 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 8ee773bb91cf7..8ec879c1e814c 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 0000000000000..e708e3d906efc --- /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 9c5743aa1451d..8bcce2c7fd6c1 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 bc2746ddf7888..8ef5b3acf4b2c 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 c2ecb5a88df8e..0478c4d49c1ba 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 4f246e5ada204..89a43f896a4d9 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 6e761bb6a51a0..0000000000000 --- 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 ffd9e587361e9..4bbc32b371108 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 29ba601a75705..5bf47929cffa5 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 0000000000000..c1b21b1de1db1 --- /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 0000000000000..05674205e1e38 --- /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 0000000000000..010a2ab50ffab --- /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 0000000000000..4df370d6bced9 --- /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 0000000000000..0a8fe775bb335 --- /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 0000000000000..690b0f698f306 --- /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 a5a8c4963f227..fdc53d6905a0b 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 52c0247b950db..7c3dc31b742ae 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 04fec0e21a467..e1ef6e5867859 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 e8dd19fd2ff8e..af354baa67903 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 e2bc1c280affc..17087d447e630 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/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index f9070b545508b..6c88019152dfd 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -58,6 +58,7 @@ import type { ValueListIndicatorMatchResponseAggregation, } from './types'; import { telemetryConfiguration } from './configuration'; +import { ENDPOINT_METRICS_INDEX } from '../../../common/constants'; export interface ITelemetryReceiver { start( @@ -277,7 +278,7 @@ export class TelemetryReceiver implements ITelemetryReceiver { const query: SearchRequest = { expand_wildcards: ['open' as const, 'hidden' as const], - index: `.ds-metrics-endpoint.metrics-*`, + index: ENDPOINT_METRICS_INDEX, ignore_unavailable: false, body: { size: 0, // no query results required - only aggregation quantity diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index dd4993807f7c3..a4d9610b774ba 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/security_solution/server/usage/collector.ts b/x-pack/plugins/security_solution/server/usage/collector.ts index 6f526051f17d1..fc3d1c98a58a6 100644 --- a/x-pack/plugins/security_solution/server/usage/collector.ts +++ b/x-pack/plugins/security_solution/server/usage/collector.ts @@ -9,11 +9,13 @@ import type { CollectorFetchContext } from '@kbn/usage-collection-plugin/server' import type { CollectorDependencies } from './types'; import { getDetectionsMetrics } from './detections/get_metrics'; import { getInternalSavedObjectsClient } from './get_internal_saved_objects_client'; +import { getEndpointMetrics } from './endpoint/get_metrics'; export type RegisterCollector = (deps: CollectorDependencies) => void; export interface UsageData { detectionMetrics: {}; + endpointMetrics: {}; } export const registerCollector: RegisterCollector = ({ @@ -2397,20 +2399,30 @@ export const registerCollector: RegisterCollector = ({ }, }, }, + endpointMetrics: { + unique_endpoint_count: { + type: 'long', + _meta: { description: 'Number of active unique endpoints in last 24 hours' }, + }, + }, }, isReady: () => true, fetch: async ({ esClient }: CollectorFetchContext): Promise => { const savedObjectsClient = await getInternalSavedObjectsClient(core); - const detectionMetrics = await getDetectionsMetrics({ - eventLogIndex, - signalsIndex, - esClient, - savedObjectsClient, - logger, - mlClient: ml, - }); + const [detectionMetrics, endpointMetrics] = await Promise.allSettled([ + getDetectionsMetrics({ + eventLogIndex, + signalsIndex, + esClient, + savedObjectsClient, + logger, + mlClient: ml, + }), + getEndpointMetrics({ esClient, logger }), + ]); return { - detectionMetrics: detectionMetrics || {}, + detectionMetrics: detectionMetrics.status === 'fulfilled' ? detectionMetrics.value : {}, + endpointMetrics: endpointMetrics.status === 'fulfilled' ? endpointMetrics.value : {}, }; }, }); diff --git a/x-pack/plugins/security_solution/server/usage/endpoint/get_metrics.mocks.ts b/x-pack/plugins/security_solution/server/usage/endpoint/get_metrics.mocks.ts new file mode 100644 index 0000000000000..e83e47bacb85f --- /dev/null +++ b/x-pack/plugins/security_solution/server/usage/endpoint/get_metrics.mocks.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import type { AggregationsAggregate } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +export const getUniqueEndpointCountMock = (): SearchResponse< + unknown, + Record +> => ({ + took: 495, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + max_score: null, + hits: [], + }, + aggregations: { + endpoint_count: { value: 3 }, + }, +}); diff --git a/x-pack/plugins/security_solution/server/usage/endpoint/get_metrics.test.ts b/x-pack/plugins/security_solution/server/usage/endpoint/get_metrics.test.ts new file mode 100644 index 0000000000000..c4a9fa8cdad42 --- /dev/null +++ b/x-pack/plugins/security_solution/server/usage/endpoint/get_metrics.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { getEndpointMetrics, getUniqueEndpointCount } from './get_metrics'; +import { getUniqueEndpointCountMock } from './get_metrics.mocks'; +import type { EndpointMetrics } from './types'; + +describe('Endpoint Metrics', () => { + let esClient: ReturnType; + let logger: ReturnType; + + describe('getEndpointMetrics()', () => { + beforeEach(() => { + esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + logger = loggingSystemMock.createLogger(); + }); + + it('returns accurate active unique endpoint count', async () => { + esClient.search.mockResponseOnce(getUniqueEndpointCountMock()); + const result = await getEndpointMetrics({ + esClient, + logger, + }); + expect(result).toEqual({ + unique_endpoint_count: 3, + }); + }); + }); + describe('getUniqueEndpointCount()', () => { + beforeEach(() => { + esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + logger = loggingSystemMock.createLogger(); + }); + + it('returns unique endpoint count', async () => { + esClient.search.mockResponseOnce(getUniqueEndpointCountMock()); + const result = await getUniqueEndpointCount(esClient, logger); + expect(esClient.search).toHaveBeenCalled(); + expect(result).toEqual(3); + }); + + it('returns 0 on error', async () => { + esClient.search.mockRejectedValueOnce(new Error('Connection Error')); + const result = await getUniqueEndpointCount(esClient, logger); + expect(esClient.search).toHaveBeenCalled(); + expect(result).toEqual(0); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/usage/endpoint/get_metrics.ts b/x-pack/plugins/security_solution/server/usage/endpoint/get_metrics.ts new file mode 100644 index 0000000000000..f78bf54cdd909 --- /dev/null +++ b/x-pack/plugins/security_solution/server/usage/endpoint/get_metrics.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient, Logger } from '@kbn/core/server'; +import type { SearchRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { EndpointMetrics, UniqueEndpointCountResponse } from './types'; +import { ENDPOINT_METRICS_INDEX } from '../../../common/constants'; +import { tlog } from '../../lib/telemetry/helpers'; + +export interface GetEndpointMetricsOptions { + esClient: ElasticsearchClient; + logger: Logger; +} + +export const getEndpointMetrics = async ({ + esClient, + logger, +}: GetEndpointMetricsOptions): Promise => { + return { + unique_endpoint_count: await getUniqueEndpointCount(esClient, logger), + }; +}; + +export const getUniqueEndpointCount = async ( + esClient: ElasticsearchClient, + logger: Logger +): Promise => { + try { + const query: SearchRequest = { + expand_wildcards: ['open' as const, 'hidden' as const], + index: ENDPOINT_METRICS_INDEX, + ignore_unavailable: false, + body: { + size: 0, // no query results required - only aggregation quantity + query: { + range: { + '@timestamp': { + gte: 'now-24h', + lt: 'now', + }, + }, + }, + aggs: { + endpoint_count: { + cardinality: { + field: 'agent.id', + }, + }, + }, + }, + }; + + const response = await esClient.search(query); + const { aggregations: endpointCountResponse } = + response as unknown as UniqueEndpointCountResponse; + return endpointCountResponse?.endpoint_count?.value ?? 0; + } catch (e) { + tlog(logger, `Failed to get active endpoint count due to: ${e.message}`); + return 0; + } +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/styles.ts b/x-pack/plugins/security_solution/server/usage/endpoint/types.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/security_solution/server/usage/endpoint/types.ts index 4f7814eb793cc..76cdb3c32d07c 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/styles.ts +++ b/x-pack/plugins/security_solution/server/usage/endpoint/types.ts @@ -5,14 +5,12 @@ * 2.0. */ -import { CSSObject } from '@emotion/react'; +export interface EndpointMetrics { + unique_endpoint_count: number; +} -export const useStyles = () => { - const button: CSSObject = { - display: 'inline-flex', +export interface UniqueEndpointCountResponse { + aggregations: { + endpoint_count: { value: number }; }; - - return { - button, - }; -}; +} 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 82b0b5c061f77..8bca3b4a05a9a 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} /> - + ( ( (
+ { + 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 ac2ae19700e41..8b60edce8b93f 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 aeb5010f3496e..274d0a9d28317 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 14aaf8c15d358..e847fdd5c2400 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 0000000000000..4dd4e5a0b19a1 --- /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 34b76441bd52b..0c9e0c418118c 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 2904e4a9f858e..6e0e15e6cdda2 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/apps/synthetics/hooks/use_enablement.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts index 74a430240b616..a89d323b63726 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_enablement.ts @@ -31,6 +31,7 @@ export function useEnablement() { canEnable: enablement?.canEnable, isEnabled: enablement?.isEnabled, }, + invalidApiKeyError: enablement ? !Boolean(enablement?.isValidApiKey) : false, error, loading, enableSynthetics: useCallback(() => dispatch(enableSynthetics()), [dispatch]), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts index 3bf9ff69bf005..06ec4dd3b26b6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts @@ -58,6 +58,7 @@ export const syntheticsEnablementReducer = createReducer(initialState, (builder) areApiKeysEnabled: state.enablement?.areApiKeysEnabled ?? false, canManageApiKeys: state.enablement?.canManageApiKeys ?? false, isEnabled: false, + isValidApiKey: true, }; }) .addCase(disableSyntheticsFailure, (state, action) => { @@ -75,6 +76,7 @@ export const syntheticsEnablementReducer = createReducer(initialState, (builder) canEnable: state.enablement?.canEnable ?? false, areApiKeysEnabled: state.enablement?.areApiKeysEnabled ?? false, canManageApiKeys: state.enablement?.canManageApiKeys ?? false, + isValidApiKey: state.enablement?.isValidApiKey ?? false, isEnabled: true, }; }) diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_enablement.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_enablement.ts index f376a8bc9023d..e9ebd839f47fd 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_enablement.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_enablement.ts @@ -37,6 +37,7 @@ export function useEnablement() { canEnable: enablement?.canEnable, isEnabled: enablement?.isEnabled, }, + invalidApiKeyError: enablement ? !Boolean(enablement?.isValidApiKey) : false, error, loading, totalMonitors: total, 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 930d89f97af2a..4c3d80a4d68cd 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 a21dedce9043a..78e46f6a0fea8 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 e708be5c8921f..5915aa27e6df5 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 7e3735834644e..68925a72f78d4 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 7fd0f0b3a410b..df402944cb462 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 16ab92a6862c2..5f10d29d4814f 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 56a6fd7e6dc30..67fdf86618641 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 23dd326b7e18b..63a2377c5a45f 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 6d2a826caa4b4..bd9a6390e7f2e 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 007dfd12427f3..823c57edfd051 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/synthetics/public/legacy_uptime/pages/monitor_management/disabled_callout.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/disabled_callout.tsx new file mode 100644 index 0000000000000..48e5564621a11 --- /dev/null +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/disabled_callout.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useSelector } from 'react-redux'; +import { monitorManagementListSelector } from '../../state/selectors'; +import { useEnablement } from '../../components/monitor_management/hooks/use_enablement'; + +export const DisabledCallout = () => { + const { enablement, enableSynthetics } = useEnablement(); + const { list: monitorList } = useSelector(monitorManagementListSelector); + + const showDisableCallout = !enablement.isEnabled && monitorList.total && monitorList.total > 0; + + if (!showDisableCallout) { + return null; + } + + return ( + <> + +

{CALLOUT_MANAGEMENT_DESCRIPTION}

+ {enablement.canEnable ? ( + { + enableSynthetics(); + }} + > + {SYNTHETICS_ENABLE_LABEL} + + ) : ( +

+ {CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '} + + {LEARN_MORE_LABEL} + +

+ )} +
+ + + ); +}; + +const LEARN_MORE_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.disabledCallout.learnMore', + { + defaultMessage: 'Learn more', + } +); + +const CALLOUT_MANAGEMENT_DISABLED = i18n.translate( + 'xpack.synthetics.monitorManagement.callout.disabled', + { + defaultMessage: 'Monitor Management is disabled', + } +); + +const CALLOUT_MANAGEMENT_CONTACT_ADMIN = i18n.translate( + 'xpack.synthetics.monitorManagement.disabledCallout.adminContact', + { + defaultMessage: 'Contact your administrator to enable Monitor Management.', + } +); + +const CALLOUT_MANAGEMENT_DESCRIPTION = i18n.translate( + 'xpack.synthetics.monitorManagement.disabledCallout.description.disabled', + { + defaultMessage: + 'Monitor Management is currently disabled and your existing monitors are paused. You can enable Monitor Management to run your monitors.', + } +); + +const SYNTHETICS_ENABLE_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.syntheticsEnableLabel.management', + { + defaultMessage: 'Enable Monitor Management', + } +); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/invalid_api_key_callout.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/invalid_api_key_callout.tsx new file mode 100644 index 0000000000000..aa8b3112955c5 --- /dev/null +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/invalid_api_key_callout.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 from 'react'; +import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useEnablement } from '../../components/monitor_management/hooks/use_enablement'; + +export const InvalidApiKeyCalloutCallout = () => { + const { enablement, enableSynthetics, invalidApiKeyError } = useEnablement(); + + if (!invalidApiKeyError || !enablement.isEnabled) { + return null; + } + + return ( + <> + +

{CALLOUT_MANAGEMENT_DESCRIPTION}

+ {enablement.canEnable ? ( + { + enableSynthetics(); + }} + > + {SYNTHETICS_ENABLE_LABEL} + + ) : ( +

+ {CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '} + + {LEARN_MORE_LABEL} + +

+ )} +
+ + + ); +}; + +const LEARN_MORE_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey', + { + defaultMessage: 'Learn more', + } +); + +const API_KEY_MISSING = i18n.translate('xpack.synthetics.monitorManagement.callout.apiKeyMissing', { + defaultMessage: 'Monitor Management is currently disabled because of missing API key', +}); + +const CALLOUT_MANAGEMENT_CONTACT_ADMIN = i18n.translate( + 'xpack.synthetics.monitorManagement.callout.disabledCallout.invalidKey', + { + defaultMessage: 'Contact your administrator to enable Monitor Management.', + } +); + +const CALLOUT_MANAGEMENT_DESCRIPTION = i18n.translate( + 'xpack.synthetics.monitorManagement.callout.description.invalidKey', + { + defaultMessage: + `Monitor Management is currently disabled. To run your monitors in one of Elastic's global managed testing locations,` + + 'you need to re-enable monitor management.', + } +); + +const SYNTHETICS_ENABLE_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey', + { + defaultMessage: 'Enable monitor management', + } +); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/monitor_management.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/monitor_management.tsx index 852819977e155..c7ff463cca95a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/monitor_management.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/monitor_management/monitor_management.tsx @@ -8,8 +8,9 @@ import React, { useEffect, useRef, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { useSelector } from 'react-redux'; -import { EuiCallOut, EuiButton, EuiSpacer, EuiLink } from '@elastic/eui'; import { useTrackPageview } from '@kbn/observability-plugin/public'; +import { InvalidApiKeyCalloutCallout } from './invalid_api_key_callout'; +import { DisabledCallout } from './disabled_callout'; import { ManageLocationsPortal } from '../../components/monitor_management/manage_locations/manage_locations'; import { monitorManagementListSelector } from '../../state/selectors'; import { useMonitorManagementBreadcrumbs } from './use_monitor_management_breadcrumbs'; @@ -27,12 +28,7 @@ export const MonitorManagementPage: React.FC = () => { useMonitorManagementBreadcrumbs(); const [shouldFocusEnablementButton, setShouldFocusEnablementButton] = useState(false); - const { - error: enablementError, - enablement, - loading: enablementLoading, - enableSynthetics, - } = useEnablement(); + const { error: enablementError, enablement, loading: enablementLoading } = useEnablement(); const { loading: locationsLoading } = useLocations(); const { list: monitorList } = useSelector(monitorManagementListSelector); const { isEnabled } = enablement; @@ -61,32 +57,8 @@ export const MonitorManagementPage: React.FC = () => { errorTitle={ERROR_HEADING_LABEL} errorBody={ERROR_HEADING_BODY} > - {!isEnabled && monitorList.total && monitorList.total > 0 ? ( - <> - -

{CALLOUT_MANAGEMENT_DESCRIPTION}

- {enablement.canEnable ? ( - { - enableSynthetics(); - }} - > - {SYNTHETICS_ENABLE_LABEL} - - ) : ( -

- {CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '} - - {LEARN_MORE_LABEL} - -

- )} -
- - - ) : null} + + => { return await apiService.delete(API_URLS.SYNTHETICS_ENABLEMENT); }; -export const fetchEnableSynthetics = async (): Promise => { +export const fetchEnableSynthetics = async (): Promise => { return await apiService.post(API_URLS.SYNTHETICS_ENABLEMENT); }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts index 7a3ae1f22cdcf..ef9f3239625ff 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/reducers/monitor_management.ts @@ -217,6 +217,7 @@ export const monitorManagementListReducer = createReducer(initialState, (builder canEnable: state.enablement?.canEnable || false, areApiKeysEnabled: state.enablement?.areApiKeysEnabled || false, isEnabled: false, + isValidApiKey: state.enablement?.isValidApiKey || false, }, })) .addCase( @@ -240,23 +241,21 @@ export const monitorManagementListReducer = createReducer(initialState, (builder enablement: true, }, })) - .addCase(enableSyntheticsSuccess, (state: WritableDraft) => ({ - ...state, - loading: { - ...state.loading, - enablement: false, - }, - error: { - ...state.error, - enablement: null, - }, - enablement: { - canManageApiKeys: state.enablement?.canManageApiKeys || false, - canEnable: state.enablement?.canEnable || false, - areApiKeysEnabled: state.enablement?.areApiKeysEnabled || false, - isEnabled: true, - }, - })) + .addCase( + enableSyntheticsSuccess, + (state: WritableDraft, action: PayloadAction) => ({ + ...state, + loading: { + ...state.loading, + enablement: false, + }, + error: { + ...state.error, + enablement: null, + }, + enablement: action.payload, + }) + ) .addCase( enableSyntheticsFailure, (state: WritableDraft, action: PayloadAction) => ({ diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/adapters/framework/adapter_types.ts index 07c247dc44514..fc088e2097ddc 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/adapters/framework/adapter_types.ts @@ -11,6 +11,7 @@ import type { IScopedClusterClient, Logger, IBasePath, + CoreStart, } from '@kbn/core/server'; import type { TelemetryPluginSetup, TelemetryPluginStart } from '@kbn/telemetry-plugin/server'; import { ObservabilityPluginSetup } from '@kbn/observability-plugin/server'; @@ -65,6 +66,7 @@ export interface UptimeServerSetup { uptimeEsClient: UptimeEsClient; basePath: IBasePath; isDev?: boolean; + coreStart: CoreStart; } export interface UptimeCorePluginsSetup { diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/index.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/index.ts index 664e0cb0c6185..5dceb0c570bc3 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/index.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/requests/index.ts @@ -28,7 +28,6 @@ import { getJourneyScreenshotBlocks } from './get_journey_screenshot_blocks'; import { getSyntheticsMonitor } from './get_monitor'; import { getSyntheticsEnablement, - deleteServiceApiKey, generateAndSaveServiceAPIKey, getAPIKeyForSyntheticsService, } from '../../../synthetics_service/get_api_key'; @@ -57,7 +56,6 @@ export const uptimeRequests = { getNetworkEvents, getSyntheticsEnablement, getAPIKeyForSyntheticsService, - deleteServiceApiKey, generateAndSaveServiceAPIKey, }; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/service_api_key.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/service_api_key.ts index 9f21aed14e322..adab53c9d4268 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/service_api_key.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/saved_objects/service_api_key.ts @@ -11,8 +11,8 @@ import { SavedObjectsErrorHelpers, SavedObjectsType, } from '@kbn/core/server'; -import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { SyntheticsServiceApiKey } from '../../../../common/runtime_types/synthetics_service_api_key'; +import { UptimeServerSetup } from '../adapters'; export const syntheticsApiKeyID = 'ba997842-b0cf-4429-aa9d-578d9bf0d391'; export const syntheticsApiKeyObjectType = 'uptime-synthetics-api-key'; @@ -49,9 +49,17 @@ export const syntheticsServiceApiKey: SavedObjectsType = { }, }; -export const getSyntheticsServiceAPIKey = async (client: EncryptedSavedObjectsClient) => { +const getEncryptedSOClient = (server: UptimeServerSetup) => { + const encryptedClient = server.encryptedSavedObjects.getClient({ + includedHiddenTypes: [syntheticsServiceApiKey.name], + }); + return encryptedClient; +}; + +const getSyntheticsServiceAPIKey = async (server: UptimeServerSetup) => { try { - const obj = await client.getDecryptedAsInternalUser( + const soClient = getEncryptedSOClient(server); + const obj = await soClient.getDecryptedAsInternalUser( syntheticsServiceApiKey.name, syntheticsApiKeyID ); @@ -64,20 +72,26 @@ export const getSyntheticsServiceAPIKey = async (client: EncryptedSavedObjectsCl } }; -export const setSyntheticsServiceApiKey = async ( - client: SavedObjectsClientContract, +const setSyntheticsServiceApiKey = async ( + soClient: SavedObjectsClientContract, apiKey: SyntheticsServiceApiKey ) => { - await client.create(syntheticsServiceApiKey.name, apiKey, { + await soClient.create(syntheticsServiceApiKey.name, apiKey, { id: syntheticsApiKeyID, overwrite: true, }); }; -export const deleteSyntheticsServiceApiKey = async (client: SavedObjectsClientContract) => { +const deleteSyntheticsServiceApiKey = async (soClient: SavedObjectsClientContract) => { try { - return await client.delete(syntheticsServiceApiKey.name, syntheticsApiKeyID); + return await soClient.delete(syntheticsServiceApiKey.name, syntheticsApiKeyID); } catch (e) { throw e; } }; + +export const syntheticsServiceAPIKeySavedObject = { + get: getSyntheticsServiceAPIKey, + set: setSyntheticsServiceApiKey, + delete: deleteSyntheticsServiceApiKey, +}; diff --git a/x-pack/plugins/synthetics/server/plugin.ts b/x-pack/plugins/synthetics/server/plugin.ts index 8871b387eb62d..dc6a300332735 100644 --- a/x-pack/plugins/synthetics/server/plugin.ts +++ b/x-pack/plugins/synthetics/server/plugin.ts @@ -121,6 +121,7 @@ export class Plugin implements PluginType { ); if (this.server) { + this.server.coreStart = coreStart; this.server.security = pluginsStart.security; this.server.fleet = pluginsStart.fleet; this.server.encryptedSavedObjects = pluginsStart.encryptedSavedObjects; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts index 0436b8ec0620e..d4650505d3721 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_api_key.ts @@ -13,11 +13,8 @@ export const getAPIKeySyntheticsRoute: SyntheticsRestApiRouteFactory = (libs) => path: API_URLS.SYNTHETICS_APIKEY, validate: {}, handler: async ({ request, server }): Promise => { - const { security } = server; - const apiKey = await generateAPIKey({ request, - security, server, uptimePrivileges: true, }); diff --git a/x-pack/plugins/synthetics/server/routes/synthetics_service/enablement.ts b/x-pack/plugins/synthetics/server/routes/synthetics_service/enablement.ts index 6d60827216e4e..defd82c415726 100644 --- a/x-pack/plugins/synthetics/server/routes/synthetics_service/enablement.ts +++ b/x-pack/plugins/synthetics/server/routes/synthetics_service/enablement.ts @@ -4,22 +4,25 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { syntheticsServiceAPIKeySavedObject } from '../../legacy_uptime/lib/saved_objects/service_api_key'; import { SyntheticsRestApiRouteFactory, UMRestApiRouteFactory, } from '../../legacy_uptime/routes/types'; import { API_URLS } from '../../../common/constants'; -import { SyntheticsForbiddenError } from '../../synthetics_service/get_api_key'; +import { + generateAndSaveServiceAPIKey, + SyntheticsForbiddenError, +} from '../../synthetics_service/get_api_key'; export const getSyntheticsEnablementRoute: UMRestApiRouteFactory = (libs) => ({ method: 'GET', path: API_URLS.SYNTHETICS_ENABLEMENT, validate: {}, - handler: async ({ request, response, server }): Promise => { + handler: async ({ response, server }): Promise => { try { return response.ok({ body: await libs.requests.getSyntheticsEnablement({ - request, server, }), }); @@ -38,25 +41,21 @@ export const disableSyntheticsRoute: SyntheticsRestApiRouteFactory = (libs) => ( response, request, server, - savedObjectsClient, syntheticsMonitorClient, + savedObjectsClient, }): Promise => { const { security } = server; const { syntheticsService } = syntheticsMonitorClient; try { - const { canEnable } = await libs.requests.getSyntheticsEnablement({ request, server }); + const { canEnable } = await libs.requests.getSyntheticsEnablement({ server }); if (!canEnable) { return response.forbidden(); } await syntheticsService.deleteAllConfigs(); - const apiKey = await libs.requests.getAPIKeyForSyntheticsService({ - server, - }); - await libs.requests.deleteServiceApiKey({ - request, + const { apiKey } = await libs.requests.getAPIKeyForSyntheticsService({ server, - savedObjectsClient, }); + await syntheticsServiceAPIKeySavedObject.delete(savedObjectsClient); await security.authc.apiKeys?.invalidate(request, { ids: [apiKey?.id || ''] }); return response.ok({}); } catch (e) { @@ -71,15 +70,18 @@ export const enableSyntheticsRoute: UMRestApiRouteFactory = (libs) => ({ path: API_URLS.SYNTHETICS_ENABLEMENT, validate: {}, handler: async ({ request, response, server }): Promise => { - const { authSavedObjectsClient, logger, security } = server; + const { authSavedObjectsClient, logger } = server; try { - await libs.requests.generateAndSaveServiceAPIKey({ + await generateAndSaveServiceAPIKey({ request, authSavedObjectsClient, - security, server, }); - return response.ok({}); + return response.ok({ + body: await libs.requests.getSyntheticsEnablement({ + server, + }), + }); } catch (e) { logger.error(e); if (e instanceof SyntheticsForbiddenError) { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/authentication/check_has_privilege.ts b/x-pack/plugins/synthetics/server/synthetics_service/authentication/check_has_privilege.ts new file mode 100644 index 0000000000000..56b7ce8b79c62 --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/authentication/check_has_privilege.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 { UptimeServerSetup } from '../../legacy_uptime/lib/adapters'; +import { getFakeKibanaRequest } from '../utils/fake_kibana_request'; +import { serviceApiKeyPrivileges } from '../get_api_key'; + +export const checkHasPrivileges = async ( + server: UptimeServerSetup, + apiKey: { id: string; apiKey: string } +) => { + return await server.coreStart.elasticsearch.client + .asScoped(getFakeKibanaRequest({ id: apiKey.id, api_key: apiKey.apiKey })) + .asCurrentUser.security.hasPrivileges({ + body: { + index: serviceApiKeyPrivileges.indices, + cluster: serviceApiKeyPrivileges.cluster, + }, + }); +}; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.test.ts index 5bd8d05951031..4b15f4da43515 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getAPIKeyForSyntheticsService } from './get_api_key'; +import { getAPIKeyForSyntheticsService, syntheticsIndex } from './get_api_key'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { securityMock } from '@kbn/security-plugin/server/mocks'; import { coreMock } from '@kbn/core/server/mocks'; @@ -13,6 +13,9 @@ import { syntheticsServiceApiKey } from '../legacy_uptime/lib/saved_objects/serv import { KibanaRequest } from '@kbn/core/server'; import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; import { getUptimeESMockClient } from '../legacy_uptime/lib/requests/test_helpers'; +import { loggerMock } from '@kbn/logging-mocks'; + +import * as authUtils from './authentication/check_has_privilege'; describe('getAPIKeyTest', function () { const core = coreMock.createStart(); @@ -20,7 +23,20 @@ describe('getAPIKeyTest', function () { const encryptedSavedObjects = encryptedSavedObjectsMock.createStart(); const request = {} as KibanaRequest; + const logger = loggerMock.create(); + + jest.spyOn(authUtils, 'checkHasPrivileges').mockResolvedValue({ + index: { + [syntheticsIndex]: { + auto_configure: true, + create_doc: true, + view_index_metadata: true, + }, + }, + } as any); + const server = { + logger, security, encryptedSavedObjects, savedObjectsClient: core.savedObjects.getScopedClient(request), @@ -28,6 +44,7 @@ describe('getAPIKeyTest', function () { } as unknown as UptimeServerSetup; security.authc.apiKeys.areAPIKeysEnabled = jest.fn().mockReturnValue(true); + security.authc.apiKeys.validate = jest.fn().mockReturnValue(true); security.authc.apiKeys.create = jest.fn().mockReturnValue({ id: 'test', name: 'service-api-key', @@ -47,7 +64,10 @@ describe('getAPIKeyTest', function () { server, }); - expect(apiKey).toEqual({ apiKey: 'qwerty', id: 'test', name: 'service-api-key' }); + expect(apiKey).toEqual({ + apiKey: { apiKey: 'qwerty', id: 'test', name: 'service-api-key' }, + isValid: true, + }); expect(encryptedSavedObjects.getClient).toHaveBeenCalledTimes(1); expect(getObject).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts index 5984bcb3b8aca..435ae4ba2a129 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/get_api_key.ts @@ -10,22 +10,19 @@ import type { } from '@elastic/elasticsearch/lib/api/types'; import { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; -import { SecurityPluginStart } from '@kbn/security-plugin/server'; import { ALL_SPACES_ID } from '@kbn/security-plugin/common/constants'; -import { - deleteSyntheticsServiceApiKey, - getSyntheticsServiceAPIKey, - setSyntheticsServiceApiKey, - syntheticsServiceApiKey, -} from '../legacy_uptime/lib/saved_objects/service_api_key'; +import { syntheticsServiceAPIKeySavedObject } from '../legacy_uptime/lib/saved_objects/service_api_key'; import { SyntheticsServiceApiKey } from '../../common/runtime_types/synthetics_service_api_key'; import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; +import { checkHasPrivileges } from './authentication/check_has_privilege'; + +export const syntheticsIndex = 'synthetics-*'; export const serviceApiKeyPrivileges = { cluster: ['monitor', 'read_ilm', 'read_pipeline'] as SecurityClusterPrivilege[], indices: [ { - names: ['synthetics-*'], + names: [syntheticsIndex], privileges: [ 'view_index_metadata', 'create_doc', @@ -41,47 +38,61 @@ export const getAPIKeyForSyntheticsService = async ({ server, }: { server: UptimeServerSetup; -}): Promise => { - const { encryptedSavedObjects } = server; - - const encryptedClient = encryptedSavedObjects.getClient({ - includedHiddenTypes: [syntheticsServiceApiKey.name], - }); - +}): Promise<{ apiKey?: SyntheticsServiceApiKey; isValid: boolean }> => { try { - const apiKey = await getSyntheticsServiceAPIKey(encryptedClient); + const apiKey = await syntheticsServiceAPIKeySavedObject.get(server); + if (apiKey) { - return apiKey; + const isValid = await server.security.authc.apiKeys.validate({ + id: apiKey.id, + api_key: apiKey.apiKey, + }); + + if (isValid) { + const { index } = await checkHasPrivileges(server, apiKey); + + const indexPermissions = index[syntheticsIndex]; + + const hasPermissions = + indexPermissions.auto_configure && + indexPermissions.create_doc && + indexPermissions.view_index_metadata; + + if (!hasPermissions) { + return { isValid: false, apiKey }; + } + } else { + server.logger.info('Synthetics api is no longer valid'); + } + + return { apiKey, isValid }; } } catch (err) { - // TODO: figure out how to handle decryption errors + server.logger.error(err); } + + return { isValid: false }; }; export const generateAPIKey = async ({ server, - security, request, uptimePrivileges = false, }: { server: UptimeServerSetup; - request?: KibanaRequest; - security: SecurityPluginStart; + request: KibanaRequest; uptimePrivileges?: boolean; }) => { + const { security } = server; const isApiKeysEnabled = await security.authc.apiKeys?.areAPIKeysEnabled(); if (!isApiKeysEnabled) { throw new Error('Please enable API keys in kibana to use synthetics service.'); } - if (!request) { - throw new Error('User authorization is needed for api key generation'); - } - if (uptimePrivileges) { return security.authc.apiKeys?.create(request, { - name: 'uptime-api-key', + name: 'synthetics-api-key (required for project monitors)', kibana_role_descriptors: { uptime_save: { elasticsearch: {}, @@ -105,13 +116,13 @@ export const generateAPIKey = async ({ }); } - const { canEnable } = await getSyntheticsEnablement({ request, server }); + const { canEnable } = await hasEnablePermissions(server); if (!canEnable) { throw new SyntheticsForbiddenError(); } return security.authc.apiKeys?.create(request, { - name: 'synthetics-api-key', + name: 'synthetics-api-key (required for monitor management)', role_descriptors: { synthetics_writer: serviceApiKeyPrivileges, }, @@ -124,68 +135,59 @@ export const generateAPIKey = async ({ export const generateAndSaveServiceAPIKey = async ({ server, - security, request, authSavedObjectsClient, }: { server: UptimeServerSetup; - request?: KibanaRequest; - security: SecurityPluginStart; + request: KibanaRequest; // authSavedObject is needed for write operations authSavedObjectsClient?: SavedObjectsClientContract; }) => { - const apiKeyResult = await generateAPIKey({ server, request, security }); + const apiKeyResult = await generateAPIKey({ server, request }); if (apiKeyResult) { const { id, name, api_key: apiKey } = apiKeyResult; const apiKeyObject = { id, name, apiKey }; if (authSavedObjectsClient) { // discard decoded key and rest of the keys - await setSyntheticsServiceApiKey(authSavedObjectsClient, apiKeyObject); + await syntheticsServiceAPIKeySavedObject.set(authSavedObjectsClient, apiKeyObject); } return apiKeyObject; } }; -export const deleteServiceApiKey = async ({ - request, - server, - savedObjectsClient, -}: { - server: UptimeServerSetup; - request?: KibanaRequest; - savedObjectsClient: SavedObjectsClientContract; -}) => { - await deleteSyntheticsServiceApiKey(savedObjectsClient); -}; - -export const getSyntheticsEnablement = async ({ - request, - server: { uptimeEsClient, security, encryptedSavedObjects }, -}: { - server: UptimeServerSetup; - request?: KibanaRequest; -}) => { - const encryptedClient = encryptedSavedObjects.getClient({ - includedHiddenTypes: [syntheticsServiceApiKey.name], - }); +export const getSyntheticsEnablement = async ({ server }: { server: UptimeServerSetup }) => { + const { security } = server; const [apiKey, hasPrivileges, areApiKeysEnabled] = await Promise.all([ - getSyntheticsServiceAPIKey(encryptedClient), - uptimeEsClient.baseESClient.security.hasPrivileges({ - body: { - cluster: [ - 'manage_security', - 'manage_api_key', - 'manage_own_api_key', - ...serviceApiKeyPrivileges.cluster, - ], - index: serviceApiKeyPrivileges.indices, - }, - }), + getAPIKeyForSyntheticsService({ server }), + hasEnablePermissions(server), security.authc.apiKeys.areAPIKeysEnabled(), ]); + const { canEnable, canManageApiKeys } = hasPrivileges; + return { + canEnable, + canManageApiKeys, + isEnabled: Boolean(apiKey?.apiKey), + isValidApiKey: apiKey?.isValid, + areApiKeysEnabled, + }; +}; + +const hasEnablePermissions = async ({ uptimeEsClient }: UptimeServerSetup) => { + const hasPrivileges = await uptimeEsClient.baseESClient.security.hasPrivileges({ + body: { + cluster: [ + 'manage_security', + 'manage_api_key', + 'manage_own_api_key', + ...serviceApiKeyPrivileges.cluster, + ], + index: serviceApiKeyPrivileges.indices, + }, + }); + const { cluster } = hasPrivileges; const { manage_security: manageSecurity, @@ -203,10 +205,8 @@ export const getSyntheticsEnablement = async ({ ); return { - canEnable: canManageApiKeys && hasClusterPermissions && hasIndexPermissions, canManageApiKeys, - isEnabled: Boolean(apiKey), - areApiKeysEnabled, + canEnable: canManageApiKeys && hasClusterPermissions && hasIndexPermissions, }; }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts index 7597797be8482..5dceb3802c5f7 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/service_api_client.ts @@ -189,6 +189,9 @@ export class ServiceAPIClient { }), catchError((err: AxiosError<{ reason: string; status: number }>) => { pushErrors.push({ locationId: id, error: err.response?.data! }); + const reason = err.response?.data?.reason ?? ''; + + err.message = `Failed to call service location ${url} with method ${method} with ${allMonitors.length} monitors: ${err.message}, ${reason}`; this.logger.error(err); sendErrorTelemetryEvents(this.logger, this.server.telemetry, { reason: err.response?.data?.reason, @@ -199,9 +202,6 @@ export class ServiceAPIClient { url, stackVersion: this.server.stackVersion, }); - if (err.response?.data?.reason) { - this.logger.error(err.response?.data?.reason); - } // we don't want to throw an unhandled exception here return of(true); }) diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts index 3136fd6cab07a..57a174f183166 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_service.ts @@ -7,35 +7,33 @@ /* eslint-disable max-classes-per-file */ -import { SavedObject } from '@kbn/core/server'; -import { Logger } from '@kbn/core/server'; +import { Logger, SavedObject } from '@kbn/core/server'; import { ConcreteTaskInstance, + TaskInstance, TaskManagerSetupContract, TaskManagerStartContract, - TaskInstance, } from '@kbn/task-manager-plugin/server'; import { Subject } from 'rxjs'; import { sendErrorTelemetryEvents } from '../routes/telemetry/monitor_upgrade_sender'; import { UptimeServerSetup } from '../legacy_uptime/lib/adapters'; import { installSyntheticsIndexTemplates } from '../routes/synthetics_service/install_index_templates'; -import { SyntheticsServiceApiKey } from '../../common/runtime_types/synthetics_service_api_key'; import { getAPIKeyForSyntheticsService } from './get_api_key'; import { syntheticsMonitorType } from '../legacy_uptime/lib/saved_objects/synthetics_monitor'; import { getEsHosts } from './get_es_hosts'; import { ServiceConfig } from '../../common/config'; import { ServiceAPIClient } from './service_api_client'; -import { formatMonitorConfig, formatHeartbeatRequest } from './formatters/format_configs'; +import { formatHeartbeatRequest, formatMonitorConfig } from './formatters/format_configs'; import { ConfigKey, + HeartbeatConfig, MonitorFields, + ServiceLocationErrors, ServiceLocations, SyntheticsMonitor, - ThrottlingOptions, SyntheticsMonitorWithId, - ServiceLocationErrors, SyntheticsMonitorWithSecrets, - HeartbeatConfig, + ThrottlingOptions, } from '../../common/runtime_types'; import { getServiceLocations } from './get_service_locations'; @@ -54,8 +52,6 @@ export class SyntheticsService { private readonly config: ServiceConfig; private readonly esHosts: string[]; - private apiKey: SyntheticsServiceApiKey | undefined; - public locations: ServiceLocations; public throttling: ThrottlingOptions | undefined; @@ -67,6 +63,8 @@ export class SyntheticsService { public syncErrors?: ServiceLocationErrors | null = []; + public invalidApiKeyError?: boolean; + constructor(server: UptimeServerSetup) { this.logger = server.logger; this.server = server; @@ -178,7 +176,7 @@ export class SyntheticsService { status: e.status, stackVersion: service.server.stackVersion, }); - throw e; + service.logger.error(e); } return { state }; @@ -235,17 +233,19 @@ export class SyntheticsService { } async getApiKey() { - try { - this.apiKey = await getAPIKeyForSyntheticsService({ server: this.server }); - } catch (err) { - this.logger.error(err); - throw err; + const { apiKey, isValid } = await getAPIKeyForSyntheticsService({ server: this.server }); + if (!isValid) { + throw new Error( + 'API key is not valid. Cannot push monitor configuration to synthetics public testing locations' + ); } - return this.apiKey; + return apiKey; } - async getOutput(apiKey: SyntheticsServiceApiKey) { + async getOutput() { + const apiKey = await this.getApiKey(); + return { hosts: this.esHosts, api_key: `${apiKey?.id}:${apiKey?.apiKey}`, @@ -255,21 +255,15 @@ export class SyntheticsService { async addConfig(config: HeartbeatConfig | HeartbeatConfig[]) { const monitors = this.formatConfigs(Array.isArray(config) ? config : [config]); - this.apiKey = await this.getApiKey(); - - if (!this.apiKey) { - return null; - } - - const data = { - monitors, - output: await this.getOutput(this.apiKey), - }; + const output = await this.getOutput(); this.logger.debug(`1 monitor will be pushed to synthetics service.`); try { - this.syncErrors = await this.apiClient.post(data); + this.syncErrors = await this.apiClient.post({ + monitors, + output, + }); return this.syncErrors; } catch (e) { this.logger.error(e); @@ -282,15 +276,10 @@ export class SyntheticsService { Array.isArray(monitorConfig) ? monitorConfig : [monitorConfig] ); - this.apiKey = await this.getApiKey(); - - if (!this.apiKey) { - return null; - } - + const output = await this.getOutput(); const data = { monitors, - output: await this.getOutput(this.apiKey), + output, isEdit: true, }; @@ -308,31 +297,32 @@ export class SyntheticsService { const subject = new Subject(); subject.subscribe(async (monitorConfigs) => { - const monitors = this.formatConfigs(monitorConfigs); - - if (monitors.length === 0) { - this.logger.debug('No monitor found which can be pushed to service.'); - return null; - } - - this.apiKey = await this.getApiKey(); + try { + const monitors = this.formatConfigs(monitorConfigs); - if (!this.apiKey) { - return null; - } + if (monitors.length === 0) { + this.logger.debug('No monitor found which can be pushed to service.'); + return null; + } - const data = { - monitors, - output: await this.getOutput(this.apiKey), - }; + const output = await this.getOutput(); - this.logger.debug(`${monitors.length} monitors will be pushed to synthetics service.`); + this.logger.debug(`${monitors.length} monitors will be pushed to synthetics service.`); - try { - service.syncErrors = await this.apiClient.put(data); + service.syncErrors = await this.apiClient.put({ + monitors, + output, + }); } catch (e) { + sendErrorTelemetryEvents(service.logger, service.server.telemetry, { + reason: 'Failed to push configs to service', + message: e?.message, + type: 'pushConfigsError', + code: e?.code, + status: e.status, + stackVersion: service.server.stackVersion, + }); this.logger.error(e); - throw e; } }); @@ -345,19 +335,13 @@ export class SyntheticsService { return; } - this.apiKey = await this.getApiKey(); - - if (!this.apiKey) { - return null; - } - - const data = { - monitors, - output: await this.getOutput(this.apiKey), - }; + const output = await this.getOutput(); try { - return await this.apiClient.runOnce(data); + return await this.apiClient.runOnce({ + monitors, + output, + }); } catch (e) { this.logger.error(e); throw e; @@ -365,21 +349,13 @@ export class SyntheticsService { } async deleteConfigs(configs: SyntheticsMonitorWithId[]) { - this.apiKey = await this.getApiKey(); - - if (!this.apiKey) { - return null; - } + const output = await this.getOutput(); const data = { + output, monitors: this.formatConfigs(configs), - output: await this.getOutput(this.apiKey), }; - const result = await this.apiClient.delete(data); - if (this.syncErrors && this.syncErrors?.length > 0) { - await this.pushConfigs(); - } - return result; + return await this.apiClient.delete(data); } async deleteAllConfigs() { diff --git a/x-pack/plugins/synthetics/server/synthetics_service/utils/fake_kibana_request.ts b/x-pack/plugins/synthetics/server/synthetics_service/utils/fake_kibana_request.ts new file mode 100644 index 0000000000000..3ea92edcbc627 --- /dev/null +++ b/x-pack/plugins/synthetics/server/synthetics_service/utils/fake_kibana_request.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Request } from '@hapi/hapi'; +import { CoreKibanaRequest } from '@kbn/core/server'; + +export function getFakeKibanaRequest(apiKey: { id: string; api_key: string }) { + const requestHeaders: Record = {}; + + requestHeaders.authorization = `ApiKey ${Buffer.from(`${apiKey.id}:${apiKey.api_key}`).toString( + 'base64' + )}`; + + return CoreKibanaRequest.from({ + headers: requestHeaders, + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + } as unknown as Request); +} 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 6bf88ebf5e53d..409f29bfc437b 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" }, @@ -13010,6 +13082,16 @@ } } } + }, + "endpointMetrics": { + "properties": { + "unique_endpoint_count": { + "type": "long", + "_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 0000000000000..52cdf25da68c2 --- /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 b69f57a7b3f47..23aac220fc5cd 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 4b405b35d26f6..6a8f723d22ece 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 16fcf4eeb5c4c..28f0072d24814 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 f1f5e3c215eb2..cc80029fc960d 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 7163b84f81a6e..8d77e98577ee7 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 d7e8ac41356d1..75da839c73dc0 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 0000000000000..1b112dc036dc3 --- /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 0000000000000..097718fcdbdb8 --- /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 0000000000000..626e37b82a46a --- /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 0000000000000..1b620cde1a75b --- /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 0000000000000..fa4ac0f96ae3c --- /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 0000000000000..ec30f6d767e26 --- /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 0000000000000..6594011182999 --- /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 0000000000000..44cce6e064a0c --- /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 0000000000000..abe2687787229 --- /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 0000000000000..9df27fe4a3f54 --- /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 0000000000000..d48da8a546ddf --- /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 0000000000000..83e7487e1185e --- /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 0000000000000..eaf41e943592b --- /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 0000000000000..95e2371904d7e --- /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 0000000000000..bee88c15aee53 --- /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 0000000000000..cec70b9a4062a --- /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 0000000000000..d8da6e4afe13c --- /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 0000000000000..9f4379b648213 --- /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 0000000000000..3318486035479 --- /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 1add89ee4e205..629bba793f116 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 0000000000000..20faa5bee623b --- /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 0000000000000..5b680a2f228e8 --- /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 0000000000000..0292cea618015 --- /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 3f920b42d43b9..47ad42847c3d6 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 08fe4b782c2c0..26b928b2ff0b2 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 81850d049d830..62e5f2c13ea54 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 c99c02ac8f8a2..2e92491c09390 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 5ed76e0281743..6fc058131af9b 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 33d49e2d68c15..673a44494ac18 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 ccdf417105b16..7298fbe3c987d 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 dfdc1ed3eabd4..a5856169a5748 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 cc9cec70d8751..52130cf52354d 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 d706aff6f6aa7..4aaa675137fc8 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 a174cc08a83ee..304dd8cdfcf8b 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 23998a25fabd2..a3b8ff5457d8f 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 3ddebe460a554..f68d67339d19a 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 cb925fff4d39c..64043dbefcef6 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 1e6afa11fa138..d852c0002e83b 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 5424568c44a70..e9a2ef7e49cda 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 9f6902c65f639..5ae88bcf6f460 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 b795e921f07cd..574418ed2758f 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 67ed617822058..2a006ccb2cc8b 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é", @@ -9945,8 +9941,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 +9978,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 +10010,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 +10254,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 +10348,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 +11432,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 +11536,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", @@ -12854,7 +12885,6 @@ "xpack.fleet.agentEnrollment.confirmation.button": "Voir les agents inscrits", "xpack.fleet.agentEnrollment.copyPolicyButton": "Copier dans le presse-papiers", "xpack.fleet.agentEnrollment.downloadDescriptionForK8s": "Copiez ou téléchargez le manifeste Kubernetes.", - "xpack.fleet.agentEnrollment.downloadManifestButtonk8s": "Télécharger le manifeste", "xpack.fleet.agentEnrollment.downloadPolicyButton": "Télécharger la politique", "xpack.fleet.agentEnrollment.downloadPolicyButtonk8s": "Télécharger le manifeste", "xpack.fleet.agentEnrollment.enrollFleetTabLabel": "Enregistrer dans Fleet", @@ -19457,7 +19487,6 @@ "xpack.ml.trainedModels.modelsList.stopSuccess": "Le déploiement pour \"{modelId}\" a bien été arrêté.", "xpack.ml.trainedModels.modelsList.successfullyDeletedMessage": "{modelsCount, plural, one {Le modèle {modelIds}} other {# modèles}} {modelsCount, plural, one {a bien été supprimé} other {ont bien été supprimés}}.", "xpack.ml.trainedModels.testModelsFlyout.langIdent.output.title": "Cela ressemble au langage {lang}", - "xpack.ml.trainedModels.testModelsFlyout.langIdent.output.titleUnknown": "Code de langage inconnu : {code}", "xpack.ml.validateJob.modal.linkToJobTipsText": "Pour en savoir plus, consultez {mlJobTipsLink}.", "xpack.ml.validateJob.modal.validateJobTitle": "Valider la tâche {title}", "xpack.ml.accessDenied.description": "Vous ne disposez pas d'autorisation pour afficher le plug-in de Machine Learning. L'accès au plug-in requiert que la fonctionnalité de Machine Learning soit visible dans cet espace.", @@ -19477,7 +19506,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", @@ -31039,7 +31067,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", @@ -31100,12 +31127,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 7a2cbd5929707..a105bd0aa0d8f 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": "高", @@ -9932,8 +9928,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 +9964,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 +9996,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 +10240,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 +10334,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 +11418,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 +11522,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", @@ -12840,7 +12871,6 @@ "xpack.fleet.agentEnrollment.confirmation.button": "登録されたエージェントを表示", "xpack.fleet.agentEnrollment.copyPolicyButton": "クリップボードにコピー", "xpack.fleet.agentEnrollment.downloadDescriptionForK8s": "Kubernetesマニフェストをコピーまたはダウンロードします。", - "xpack.fleet.agentEnrollment.downloadManifestButtonk8s": "マニフェストのダウンロード", "xpack.fleet.agentEnrollment.downloadPolicyButton": "ポリシーのダウンロード", "xpack.fleet.agentEnrollment.downloadPolicyButtonk8s": "マニフェストのダウンロード", "xpack.fleet.agentEnrollment.enrollFleetTabLabel": "Fleetで登録", @@ -19438,7 +19468,6 @@ "xpack.ml.trainedModels.modelsList.stopFailed": "\"{modelId}\"の停止に失敗しました", "xpack.ml.trainedModels.modelsList.stopSuccess": "\"{modelId}\"のデプロイが正常に停止しました。", "xpack.ml.trainedModels.testModelsFlyout.langIdent.output.title": "これは{lang}のようになります", - "xpack.ml.trainedModels.testModelsFlyout.langIdent.output.titleUnknown": "不明な言語コード:{code}", "xpack.ml.validateJob.modal.linkToJobTipsText": "詳細は {mlJobTipsLink} をご覧ください。", "xpack.ml.validateJob.modal.validateJobTitle": "ジョブ {title} の検証", "xpack.ml.accessDenied.description": "機械学習プラグインを表示するアクセス権がありません。プラグインにアクセスするには、機械学習機能をこのスペースで表示する必要があります。", @@ -19458,7 +19487,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": "上位の一致に中間結果が含まれるかどうかを示します", @@ -31015,7 +31043,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": "場所を削除", @@ -31076,12 +31103,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 af00cf9497094..de988524c860b 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": "高", @@ -9950,8 +9946,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 +9983,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 +10015,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 +10259,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 +10353,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 +11437,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 +11541,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", @@ -12860,7 +12891,6 @@ "xpack.fleet.agentEnrollment.confirmation.button": "查看注册的代理", "xpack.fleet.agentEnrollment.copyPolicyButton": "复制到剪贴板", "xpack.fleet.agentEnrollment.downloadDescriptionForK8s": "复制或下载 Kubernetes 清单。", - "xpack.fleet.agentEnrollment.downloadManifestButtonk8s": "下载清单", "xpack.fleet.agentEnrollment.downloadPolicyButton": "下载策略", "xpack.fleet.agentEnrollment.downloadPolicyButtonk8s": "下载清单", "xpack.fleet.agentEnrollment.enrollFleetTabLabel": "在 Fleet 中注册", @@ -19468,7 +19498,6 @@ "xpack.ml.trainedModels.modelsList.stopSuccess": "已成功停止“{modelId}”的部署。", "xpack.ml.trainedModels.modelsList.successfullyDeletedMessage": "{modelsCount, plural, one {模型 {modelIds}} other {# 个模型}}{modelsCount, plural, other {已}}成功删除", "xpack.ml.trainedModels.testModelsFlyout.langIdent.output.title": "这像是 {lang}", - "xpack.ml.trainedModels.testModelsFlyout.langIdent.output.titleUnknown": "语言代码未知:{code}", "xpack.ml.validateJob.modal.linkToJobTipsText": "有关更多信息,请参阅 {mlJobTipsLink}。", "xpack.ml.validateJob.modal.validateJobTitle": "验证作业 {title}", "xpack.ml.accessDenied.description": "您无权查看 Machine Learning 插件。要访问该插件,需要 Machine Learning 功能在此工作区中可见。", @@ -19488,7 +19517,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": "表示排名靠前的命中是否包含中间结果", @@ -31050,7 +31078,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": "删除位置", @@ -31111,12 +31138,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 dc260578641f8..049900bd30f4d 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 ed2a1d7b17e14..cde89f97e53da 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 8c7e5a500bf0e..6a0f9b1c8f140 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 d0c61c884e528..b7bc34819836d 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 072c539ae90f5..45ac0a1d3c3b5 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 9c123a822a541..4b36a8e8aeca3 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 bfa28ca2a97a4..42ae5bedc0747 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 7f63fcbaa3049..cc236ef876ee6 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 0000000000000..0de3b56f8693d --- /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 0000000000000..f6e5d85f8230a --- /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 b5afe76889a26..9f514893d1836 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 4e37f9a02fb80..c04b55ff6db49 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 ff181a124d0fe..09b053608542a 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 c3ec9070681da..1426207b715e0 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 a49b0a489bfec..8159501b3d4b4 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 dab59919092a2..cce9fd3e5ac97 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 171b514429cd7..46d2c32ccdbdd 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 fa93ae18ec701..aac9176366042 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 02c740d2b6cc8..d7540150c90ba 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 df0d35c5961f7..ab99a727fdebf 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 20f3612f3a41b..c7b4c84f64c30 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 bcca56ad0027e..956021ddc6754 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 c51109bd11514..718222636830a 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 0696f857261ec..783b985c97346 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 d82915f2fd59d..9640b6704ef80 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 14591c7a15ccd..c4bbc7e720500 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 6d1b38a7ae94d..af4325c906456 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 6a6563877b76f..370cc6953d26c 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 ed2b6c27a0604..d37e41ff26555 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 bba71fd6ec55f..b3c09ded1bbfa 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 54957724a186d..37a1af034a7b7 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 e5bb7ffd1b0e4..76451421c9c8e 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 938349702f90c..ef44ec07656f7 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 ed5bac05c4ede..a180610784b04 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 c88070cceafee..39089c2168cb3 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 0000000000000..dd8a99b4d3a60 --- /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 0000000000000..7e01311b71c1d --- /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 f171f36ebb8dd..e888875e77115 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 0000000000000..33ee761b828dc --- /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 0000000000000..759e16c7f696d --- /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 8198d974a8592..545b7d7141a92 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 e69107e060daf..07675befd8d4f 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 18e2f240dac21..a35599e992786 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 f7af1fbbde257..aa5fe263e084f 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 0000000000000..91af1e05ece20 --- /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 0000000000000..f3e6419e4e2fa --- /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 d8ebc2298dca9..9bfc31db44c55 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 a0905a8645fbc..415672324e648 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 b4acb7e88d1a3..39d4b5602b140 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 0000000000000..bc17793717282 --- /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 524608b130779..191a8b403c41b 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 0000000000000..a99aaf1352dee --- /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 0000000000000..329782a79c3c3 --- /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 6265cb7d34ff9..063301d5f751a 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/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts index 84fdcb7bbe95b..b01986651e904 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts @@ -22,9 +22,7 @@ export default function createRegisteredRuleTypeTests({ getService }: FtrProvide .expect(200) .then((response) => response.body); - expect( - registeredRuleTypes.filter((ruleType: string) => !ruleType.startsWith('test.')) - ).to.eql([ + const ruleTypes = [ 'example.always-firing', 'transform_health', '.index-threshold', @@ -66,7 +64,11 @@ export default function createRegisteredRuleTypeTests({ getService }: FtrProvide 'apm.anomaly', 'apm.error_rate', 'apm.transaction_error_rate', - ]); + ]; + + expect( + registeredRuleTypes.sort().filter((ruleType: string) => !ruleType.startsWith('test.')) + ).to.eql(ruleTypes.sort()); }); }); } diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 242b70d3f48d9..0ce41bfe94800 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -38,7 +38,6 @@ export default function ({ getService }: FtrProviderContext) { 'actions_log_management_read', 'host_isolation_all', 'process_operations_all', - 'file_operations_all', ], uptime: ['all', 'read', 'minimal_all', 'minimal_read'], securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_delete'], @@ -84,8 +83,7 @@ export default function ({ getService }: FtrProviderContext) { }; describe('Privileges', () => { - // FLAKY: https://github.com/elastic/kibana/issues/145135 - describe.skip('GET /api/security/privileges', () => { + describe('GET /api/security/privileges', () => { it('should return a privilege map with all known privileges, without actions', async () => { // If you're adding a privilege to the following, that's great! // If you're removing a privilege, this breaks backwards compatibility @@ -194,8 +192,7 @@ export default function ({ getService }: FtrProviderContext) { }); // In this non-Basic case, results should be exactly the same as not supplying the respectLicenseLevel flag - // FLAKY: https://github.com/elastic/kibana/issues/145136 - describe.skip('GET /api/security/privileges?respectLicenseLevel=false', () => { + describe('GET /api/security/privileges?respectLicenseLevel=false', () => { it('should return a privilege map with all known privileges, without actions', async () => { // If you're adding a privilege to the following, that's great! // If you're removing a privilege, this breaks backwards compatibility diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index 2e1b01a6bc715..afec9c413fc67 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -13,8 +13,7 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); describe('Privileges', () => { - // FLAKY: https://github.com/elastic/kibana/issues/145134 - describe.skip('GET /api/security/privileges', () => { + describe('GET /api/security/privileges', () => { it('should return a privilege map with all known privileges, without actions', async () => { // If you're adding a privilege to the following, that's great! // If you're removing a privilege, this breaks backwards compatibility @@ -101,7 +100,6 @@ export default function ({ getService }: FtrProviderContext) { 'actions_log_management_all', 'actions_log_management_read', 'all', - 'file_operations_all', 'host_isolation_all', 'minimal_all', 'minimal_read', 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 f700967d060be..575de013a96dd 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/api_integration/apis/synthetics/synthetics_enablement.ts b/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts index b3566f64574bf..dac32af21106a 100644 --- a/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts +++ b/x-pack/test/api_integration/apis/synthetics/synthetics_enablement.ts @@ -23,7 +23,7 @@ export default function ({ getService }: FtrProviderContext) { describe('[GET] - /internal/uptime/service/enablement', () => { ['manage_security', 'manage_api_key', 'manage_own_api_key'].forEach((privilege) => { - it(`returns response for an admin with priviledge ${privilege}`, async () => { + it(`returns response for an admin with privilege ${privilege}`, async () => { const username = 'admin'; const roleName = `synthetics_admin-${privilege}`; const password = `${username}-password`; @@ -60,6 +60,7 @@ export default function ({ getService }: FtrProviderContext) { canManageApiKeys: true, canEnable: true, isEnabled: false, + isValidApiKey: false, }); } finally { await security.user.delete(username); @@ -102,6 +103,7 @@ export default function ({ getService }: FtrProviderContext) { canManageApiKeys: false, canEnable: false, isEnabled: false, + isValidApiKey: false, }); } finally { await security.role.delete(roleName); @@ -153,6 +155,7 @@ export default function ({ getService }: FtrProviderContext) { canManageApiKeys: true, canEnable: true, isEnabled: true, + isValidApiKey: true, }); } finally { await supertest @@ -203,6 +206,7 @@ export default function ({ getService }: FtrProviderContext) { canManageApiKeys: false, canEnable: false, isEnabled: false, + isValidApiKey: false, }); } finally { await security.user.delete(username); @@ -259,6 +263,7 @@ export default function ({ getService }: FtrProviderContext) { canManageApiKeys: true, canEnable: true, isEnabled: false, + isValidApiKey: false, }); } finally { await security.user.delete(username); @@ -308,6 +313,7 @@ export default function ({ getService }: FtrProviderContext) { canManageApiKeys: false, canEnable: false, isEnabled: true, + isValidApiKey: true, }); } finally { await supertestWithAuth @@ -370,6 +376,7 @@ export default function ({ getService }: FtrProviderContext) { canManageApiKeys: true, canEnable: true, isEnabled: false, + isValidApiKey: false, }); // can disable synthetics in non default space when enabled in default space @@ -394,6 +401,7 @@ export default function ({ getService }: FtrProviderContext) { canManageApiKeys: true, canEnable: true, isEnabled: false, + isValidApiKey: false, }); } finally { await security.user.delete(username); 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 e8e06b10794c9..e9c8167b0a878 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 d0eea80dcf1c0..a4edfd1d5ab00 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 cd961fce7aed0..c7ac470f1c8f2 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 82cb42c0039c3..5a9777e7f2e79 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 8090e4d2ce709..8ef0bf6b736dd 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 48682e6b1e8b0..2d9cdc0137546 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 9e869a91bf0b1..39b8d2acf088e 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/lens/open_in_lens/tsvb/table.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts index e3d52852cd61b..29884da19a1fa 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts @@ -82,13 +82,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(await visualize.hasNavigateToLensButton()).to.be(false); }); - it('should not allow converting series with different aggregation fucntion or aggregation by', async () => { + it('should not allow converting series with different aggregation function or aggregation by', async () => { await visualBuilder.createNewAggSeries(); await visualBuilder.selectAggType('Static Value', 1); await visualBuilder.setStaticValue(10); + await header.waitUntilLoadingHasFinished(); await visualBuilder.clickSeriesOption(); await visualBuilder.setFieldForAggregateBy('bytes'); await visualBuilder.setFunctionForAggregateFunction('Sum'); + await header.waitUntilLoadingHasFinished(); await visualBuilder.clickSeriesOption(1); await visualBuilder.setFieldForAggregateBy('bytes'); await visualBuilder.setFunctionForAggregateFunction('Min'); 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 8b2a7f3aa2ddb..6dcfcba1a66ba 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 9378e36dc04f4..2b16939deadc6 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 84531b7146eee..6b285f05ad87a 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 0000000000000..0b8fc95ceaa04 --- /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 254a883ebf920..9eb54eeb9c7e3 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/security_solution/suppression/data.json b/x-pack/test/functional/es_archives/security_solution/suppression/data.json new file mode 100644 index 0000000000000..6c22353d8227b --- /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 0000000000000..3222d9bcc490a --- /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 ace274f5cabf7..847e34f305c6c 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 ee111bfc9d0c6..47006f7a1a7d1 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 6ca556876d0e7..55cbf68ead3e0 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 7052dcba7ff23..8c386e99423ce 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 c387599d50584..8c7bb633a2fa2 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 4684b8ffc176c..c6affe8bf3c3a 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 0673e28888d10..00310d7a69938 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 0000000000000..ce8179a050c47 --- /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 7be4ce2243303..22a7a5d7567ef 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 6e3ca0d718b6e..505d9734593b9 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 8a86c5fc65f15..08c1f708c063b 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 588ff9a6e9f92..39b653784e331 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 5ab9d032c55f2..f62544a42456e 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(