diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index b03abf9a15c67..734bbce3ff84d 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -76,7 +76,6 @@ enabled: - test/functional/apps/dashboard/group3/config.ts - test/functional/apps/dashboard/group4/config.ts - test/functional/apps/dashboard/group5/config.ts - - test/functional/apps/dashboard/group6/config.ts - test/functional/apps/discover/ccs_compatibility/config.ts - test/functional/apps/discover/classic/config.ts - test/functional/apps/discover/embeddable/config.ts @@ -123,6 +122,7 @@ enabled: - x-pack/test/alerting_api_integration/basic/config.ts - x-pack/test/alerting_api_integration/security_and_spaces/group1/config.ts - x-pack/test/alerting_api_integration/security_and_spaces/group2/config.ts + - x-pack/test/alerting_api_integration/security_and_spaces/group3/config.ts - x-pack/test/alerting_api_integration/security_and_spaces/group2/config_non_dedicated_task_runner.ts - x-pack/test/alerting_api_integration/spaces_only/config.ts - x-pack/test/api_integration_basic/config.ts diff --git a/.buildkite/pipelines/performance/daily.yml b/.buildkite/pipelines/performance/daily.yml index ea7e406ba63d8..70929ddc43be6 100644 --- a/.buildkite/pipelines/performance/daily.yml +++ b/.buildkite/pipelines/performance/daily.yml @@ -27,12 +27,6 @@ steps: - exit_status: '*' limit: 1 - - label: '🚢 Performance Tests dataset extraction for scalability benchmarking' - command: .buildkite/scripts/steps/functional/scalability_dataset_extraction.sh - agents: - queue: n2-2 - depends_on: tests - - label: '📈 Report performance metrics to ci-stats' command: .buildkite/scripts/steps/functional/report_performance_metrics.sh agents: diff --git a/.buildkite/pipelines/performance/data_set_extraction_daily.yml b/.buildkite/pipelines/performance/data_set_extraction_daily.yml new file mode 100644 index 0000000000000..77d410ab29c2e --- /dev/null +++ b/.buildkite/pipelines/performance/data_set_extraction_daily.yml @@ -0,0 +1,43 @@ +steps: + - label: ':male-mechanic::skin-tone-2: Pre-Build' + command: .buildkite/scripts/lifecycle/pre_build.sh + agents: + queue: kibana-default + timeout_in_minutes: 10 + + - wait + + - label: ':building_construction: Build Kibana Distribution and Plugins' + command: .buildkite/scripts/steps/build_kibana.sh + agents: + queue: c2-16 + key: build + if: "build.env('KIBANA_BUILD_ID') == null || build.env('KIBANA_BUILD_ID') == ''" + + - label: ':kibana: Performance Tests with Playwright config' + command: .buildkite/scripts/steps/functional/performance_playwright.sh + agents: + queue: n2-2-spot + depends_on: build + key: tests + timeout_in_minutes: 60 + retry: + automatic: + - exit_status: '-1' + limit: 3 + - exit_status: '*' + limit: 1 + + - label: ':ship: Single user journeys dataset extraction for scalability benchmarking' + command: .buildkite/scripts/steps/functional/scalability_dataset_extraction.sh + agents: + queue: n2-2 + depends_on: tests + + - wait: ~ + continue_on_failure: true + + - label: ':male_superhero::skin-tone-2: Post-Build' + command: .buildkite/scripts/lifecycle/post_build.sh + agents: + queue: kibana-default diff --git a/.buildkite/pipelines/pull_request/kbn_handlebars.yml b/.buildkite/pipelines/pull_request/kbn_handlebars.yml new file mode 100644 index 0000000000000..ecc5103619216 --- /dev/null +++ b/.buildkite/pipelines/pull_request/kbn_handlebars.yml @@ -0,0 +1,11 @@ +steps: + - command: .buildkite/scripts/steps/test/kbn_handlebars.sh + label: 'Check @kbn/handlebars for upstream differences' + agents: + queue: n2-2-spot + depends_on: build + timeout_in_minutes: 5 + retry: + automatic: + - exit_status: '*' + limit: 1 diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.ts b/.buildkite/scripts/pipelines/pull_request/pipeline.ts index dc33d470b8b50..10c643b151b3f 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.ts +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.ts @@ -54,6 +54,10 @@ const uploadPipeline = (pipelineContent: string | object) => { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/base.yml', false)); + if (await doAnyChangesMatch([/^packages\/kbn-handlebars/])) { + pipeline.push(getPipeline('.buildkite/pipelines/pull_request/kbn_handlebars.yml')); + } + if ( (await doAnyChangesMatch([ /^packages\/kbn-securitysolution-.*/, diff --git a/.buildkite/scripts/steps/scalability/benchmarking.sh b/.buildkite/scripts/steps/scalability/benchmarking.sh index 3fbf1896d1877..e47e0bc10a3c5 100755 --- a/.buildkite/scripts/steps/scalability/benchmarking.sh +++ b/.buildkite/scripts/steps/scalability/benchmarking.sh @@ -74,53 +74,9 @@ download_artifacts echo "--- Clone kibana-load-testing repo and compile project" checkout_and_compile_load_runner -echo "--- Run Scalability Tests with Elasticsearch started only once and Kibana restart before each journey" +echo "--- Run Scalability Tests" cd "$KIBANA_DIR" -node scripts/es snapshot& - -esPid=$! -# Set trap on EXIT to stop Elasticsearch process -trap "kill -9 $esPid" EXIT - -# unset env vars defined in other parts of CI for automatic APM collection of -# Kibana. We manage APM config in our FTR config and performance service, and -# APM treats config in the ENV with a very high precedence. -unset ELASTIC_APM_ENVIRONMENT -unset ELASTIC_APM_TRANSACTION_SAMPLE_RATE -unset ELASTIC_APM_SERVER_URL -unset ELASTIC_APM_SECRET_TOKEN -unset ELASTIC_APM_ACTIVE -unset ELASTIC_APM_CONTEXT_PROPAGATION_ONLY -unset ELASTIC_APM_GLOBAL_LABELS -unset ELASTIC_APM_MAX_QUEUE_SIZE -unset ELASTIC_APM_METRICS_INTERVAL -unset ELASTIC_APM_CAPTURE_SPAN_STACK_TRACES -unset ELASTIC_APM_BREAKDOWN_METRICS - - -export TEST_ES_DISABLE_STARTUP=true -ES_HOST="localhost:9200" -export TEST_ES_URL="http://elastic:changeme@${ES_HOST}" -# Overriding Gatling default configuration -export ES_URL="http://${ES_HOST}" - -# Pings the ES server every second for 2 mins until its status is green -curl --retry 120 \ - --retry-delay 1 \ - --retry-connrefused \ - -I -XGET "${TEST_ES_URL}/_cluster/health?wait_for_nodes=>=1&wait_for_status=yellow" - -export ELASTIC_APM_ACTIVE=true - -for journey in scalability_traces/server/*; do - export SCALABILITY_JOURNEY_PATH="$KIBANA_DIR/$journey" - echo "--- Run scalability file: $SCALABILITY_JOURNEY_PATH" - node scripts/functional_tests \ - --config x-pack/test/scalability/config.ts \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ - --logToFile \ - --debug -done +node scripts/run_scalability --kibana-install-dir "$KIBANA_BUILD_LOCATION" --journey-config-path "scalability_traces/server" echo "--- Upload test results" upload_test_results diff --git a/.buildkite/scripts/steps/test/kbn_handlebars.sh b/.buildkite/scripts/steps/test/kbn_handlebars.sh new file mode 100755 index 0000000000000..0e7fc6ebb0648 --- /dev/null +++ b/.buildkite/scripts/steps/test/kbn_handlebars.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +echo '--- Checking for @kbn/handlebars test changes' +packages/kbn-handlebars/scripts/check_for_test_changes.sh diff --git a/.eslintignore b/.eslintignore index 66aca11d9dba8..2e7bee2f74f32 100644 --- a/.eslintignore +++ b/.eslintignore @@ -39,7 +39,7 @@ snapshots.js /packages/kbn-test/src/functional_test_runner/lib/config/__tests__/fixtures/ /packages/kbn-ui-framework/dist /packages/kbn-flot-charts/lib -/packages/kbn-monaco/src/painless/antlr +/packages/kbn-monaco/src/**/antlr # Bazel /bazel-* diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index ff19463641bdf..d16b220fbc52c 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-12-06 +date: 2022-12-08 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 4a35439d40109..719b8060f78ed 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 9db17d6564262..3065f13b19a3f 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index df8867d62870f..6cae0aac4224c 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -1186,7 +1186,7 @@ }, "" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1208,7 +1208,7 @@ }, " | undefined" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts", "deprecated": false, "trackAdoption": false }, @@ -1229,7 +1229,7 @@ }, "[]" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts", "deprecated": false, "trackAdoption": false }, @@ -1243,7 +1243,7 @@ "signature": [ "RuleParamsModifier | undefined" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts", "deprecated": false, "trackAdoption": false } @@ -1267,7 +1267,7 @@ }, "" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1281,7 +1281,7 @@ "signature": [ "string[]" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts", "deprecated": false, "trackAdoption": false }, @@ -1302,7 +1302,7 @@ }, "[]" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts", "deprecated": false, "trackAdoption": false }, @@ -1316,7 +1316,7 @@ "signature": [ "RuleParamsModifier | undefined" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts", "deprecated": false, "trackAdoption": false } @@ -1330,7 +1330,7 @@ "tags": [], "label": "BulkOperationError", "description": [], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1341,7 +1341,7 @@ "tags": [], "label": "message", "description": [], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/types.ts", "deprecated": false, "trackAdoption": false }, @@ -1355,7 +1355,7 @@ "signature": [ "number | undefined" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/types.ts", "deprecated": false, "trackAdoption": false }, @@ -1369,7 +1369,7 @@ "signature": [ "{ id: string; name: string; }" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/types.ts", "deprecated": false, "trackAdoption": false } @@ -1393,7 +1393,7 @@ }, "" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/find.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1404,7 +1404,7 @@ "tags": [], "label": "page", "description": [], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/find.ts", "deprecated": false, "trackAdoption": false }, @@ -1415,7 +1415,7 @@ "tags": [], "label": "perPage", "description": [], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/find.ts", "deprecated": false, "trackAdoption": false }, @@ -1426,7 +1426,7 @@ "tags": [], "label": "total", "description": [], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/find.ts", "deprecated": false, "trackAdoption": false }, @@ -1447,7 +1447,7 @@ }, "[]" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/find.ts", "deprecated": false, "trackAdoption": false } @@ -2753,7 +2753,9 @@ "label": "BulkEditOperation", "description": [], "signature": [ - "{ operation: \"delete\" | \"set\" | \"add\"; field: \"tags\"; value: string[]; } | { operation: \"set\" | \"add\"; field: \"actions\"; value: NormalizedAlertAction[]; } | { operation: \"set\"; field: \"schedule\"; value: ", + "{ operation: \"delete\" | \"set\" | \"add\"; field: \"tags\"; value: string[]; } | { operation: \"set\" | \"add\"; field: \"actions\"; value: ", + "NormalizedAlertAction", + "[]; } | { operation: \"set\"; field: \"schedule\"; value: ", { "pluginId": "alerting", "scope": "common", @@ -2771,7 +2773,7 @@ }, "; } | { operation: \"delete\"; field: \"snoozeSchedule\"; value?: string[] | undefined; } | { operation: \"set\"; field: \"apiKey\"; value?: undefined; }" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2801,7 +2803,7 @@ }, "" ], - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2910,7 +2912,9 @@ "section": "def-common.RuleTypeParams", "text": "RuleTypeParams" }, - " = never>({ id, includeLegacyId, includeSnoozeData, excludeFromPublicApi, }: { id: string; includeLegacyId?: boolean | undefined; includeSnoozeData?: boolean | undefined; excludeFromPublicApi?: boolean | undefined; }) => Promise<", + " = never>(params: ", + "GetParams", + ") => Promise<", { "pluginId": "alerting", "scope": "common", @@ -2920,9 +2924,9 @@ }, " | ", "SanitizedRuleWithLegacyId", - ">; delete: ({ id }: { id: string; }) => Promise<{}>; aggregate: ({ options: { fields, filter, ...options }, }?: { options?: ", + ">; delete: (params: { id: string; }) => Promise<{}>; aggregate: (params?: { options?: ", "AggregateOptions", - " | undefined; }) => Promise<", + " | undefined; } | undefined) => Promise<", "AggregateResult", ">; create: ({ data, options, }: ", + " = never>(params: ", "CreateOptions", ") => Promise<", { @@ -2950,9 +2954,9 @@ "section": "def-common.RuleTypeParams", "text": "RuleTypeParams" }, - " = never>({ options: { fields, ...options }, excludeFromPublicApi, includeSnoozeData, }?: { options?: ", - "FindOptions", - " | undefined; excludeFromPublicApi?: boolean | undefined; includeSnoozeData?: boolean | undefined; }) => Promise<", + " = never>(params?: ", + "FindParams", + " | undefined) => Promise<", { "pluginId": "alerting", "scope": "server", @@ -2968,7 +2972,7 @@ "section": "def-common.RuleTypeParams", "text": "RuleTypeParams" }, - " = never>({ id, data, }: ", + " = never>(params: ", "UpdateOptions", ") => Promise<", { @@ -2986,7 +2990,9 @@ "section": "def-common.RuleTypeParams", "text": "RuleTypeParams" }, - " = never>({ id, includeLegacyId, includeSnoozeData, }: { id: string; includeLegacyId?: boolean | undefined; includeSnoozeData?: boolean | undefined; }) => Promise<", + " = never>(params: ", + "ResolveParams", + ") => Promise<", { "pluginId": "alerting", "scope": "common", @@ -2994,7 +3000,7 @@ "section": "def-common.ResolvedSanitizedRule", "text": "ResolvedSanitizedRule" }, - ">; enable: ({ id }: { id: string; }) => Promise; disable: ({ id }: { id: string; }) => Promise; clone: >; enable: (options: { id: string; }) => Promise; disable: (options: { id: string; }) => Promise; clone: (id: string, { newId }: { newId?: string | undefined; }) => Promise<", + " = never>(args_0: string, args_1: { newId?: string | undefined; }) => Promise<", { "pluginId": "alerting", "scope": "common", @@ -3010,7 +3016,9 @@ "section": "def-common.SanitizedRule", "text": "SanitizedRule" }, - ">; muteAll: ({ id }: { id: string; }) => Promise; getAlertState: ({ id }: { id: string; }) => Promise; getAlertSummary: ({ id, dateStart, numberOfExecutions, }: ", + ">; muteAll: (options: { id: string; }) => Promise; getAlertState: (params: ", + "GetAlertStateParams", + ") => Promise; getAlertSummary: (params: ", "GetAlertSummaryParams", ") => Promise<", { @@ -3020,7 +3028,7 @@ "section": "def-common.AlertSummary", "text": "AlertSummary" }, - ">; getExecutionLogForRule: ({ id, dateStart, dateEnd, filter, page, perPage, sort, }: ", + ">; getExecutionLogForRule: (params: ", "GetExecutionLogByIdParams", ") => Promise<", { @@ -3030,7 +3038,7 @@ "section": "def-common.IExecutionLogResult", "text": "IExecutionLogResult" }, - ">; getGlobalExecutionLogWithAuth: ({ dateStart, dateEnd, filter, page, perPage, sort, namespaces, }: ", + ">; getGlobalExecutionLogWithAuth: (params: ", "GetGlobalExecutionLogParams", ") => Promise<", { @@ -3040,7 +3048,11 @@ "section": "def-common.IExecutionLogResult", "text": "IExecutionLogResult" }, - ">; getActionErrorLog: ({ id, dateStart, dateEnd, filter, page, perPage, sort, }: ", + ">; getRuleExecutionKPI: (params: ", + "GetRuleExecutionKPIParams", + ") => Promise<{ success: number; unknown: number; failure: number; warning: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; getGlobalExecutionKpiWithAuth: (params: ", + "GetGlobalExecutionKPIParams", + ") => Promise<{ success: number; unknown: number; failure: number; warning: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; getActionErrorLog: (params: ", "GetActionErrorLogByIdParams", ") => Promise<", { @@ -3050,7 +3062,7 @@ "section": "def-common.IExecutionErrorsResult", "text": "IExecutionErrorsResult" }, - ">; getActionErrorLogWithAuth: ({ id, dateStart, dateEnd, filter, page, perPage, sort, namespace, }: ", + ">; getActionErrorLogWithAuth: (params: ", "GetActionErrorLogByIdParams", ") => Promise<", { @@ -3060,11 +3072,7 @@ "section": "def-common.IExecutionErrorsResult", "text": "IExecutionErrorsResult" }, - ">; getGlobalExecutionKpiWithAuth: ({ dateStart, dateEnd, filter, namespaces, }: ", - "GetGlobalExecutionKPIParams", - ") => Promise<{ success: number; unknown: number; failure: number; warning: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; getRuleExecutionKPI: ({ id, dateStart, dateEnd, filter }: ", - "GetRuleExecutionKPIParams", - ") => Promise<{ success: number; unknown: number; failure: number; warning: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; bulkDeleteRules: (options: ", + ">; bulkDeleteRules: (options: ", "BulkOptions", ") => Promise<{ errors: ", { @@ -3074,7 +3082,17 @@ "section": "def-server.BulkOperationError", "text": "BulkOperationError" }, - "[]; total: number; taskIdsFailedToBeDeleted: string[]; }>; bulkEdit: | ", + "RuleWithLegacyId", + ")[]; total: number; taskIdsFailedToBeDeleted: string[]; }>; bulkEdit: | ", "RuleWithLegacyId", - ")[]; total: number; }>; updateApiKey: ({ id }: { id: string; }) => Promise; snooze: ({ id, snoozeSchedule, }: { id: string; snoozeSchedule: ", - { - "pluginId": "alerting", - "scope": "common", - "docId": "kibAlertingPluginApi", - "section": "def-common.RuleSnoozeSchedule", - "text": "RuleSnoozeSchedule" - }, - "; }) => Promise; unsnooze: ({ id, scheduleIds, }: { id: string; scheduleIds?: string[] | undefined; }) => Promise; calculateIsSnoozedUntil: (rule: { muteAll: boolean; snoozeSchedule?: ", - { - "pluginId": "alerting", - "scope": "common", - "docId": "kibAlertingPluginApi", - "section": "def-common.RuleSnooze", - "text": "RuleSnooze" - }, - " | undefined; }) => string | null; clearExpiredSnoozes: ({ id }: { id: string; }) => Promise; unmuteAll: ({ id }: { id: string; }) => Promise; muteInstance: ({ alertId, alertInstanceId }: ", + ")[]; total: number; }>; updateApiKey: (options: { id: string; }) => Promise; snooze: (options: ", + "SnoozeParams", + ") => Promise; unsnooze: (options: ", + "UnsnoozeParams", + ") => Promise; clearExpiredSnoozes: (options: { id: string; }) => Promise; unmuteAll: (options: { id: string; }) => Promise; muteInstance: (options: ", "MuteOptions", - ") => Promise; unmuteInstance: ({ alertId, alertInstanceId }: ", + ") => Promise; unmuteInstance: (options: ", "MuteOptions", - ") => Promise; runSoon: ({ id }: { id: string; }) => Promise; listAlertTypes: () => Promise Promise; runSoon: (options: { id: string; }) => Promise; listAlertTypes: () => Promise>; getSpaceId: () => string | undefined; }" ], diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index cf05c3d4a8c3d..9f8a7da77773c 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-12-06 +date: 2022-12-08 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 | |-------------------|-----------|------------------------|-----------------| -| 417 | 0 | 408 | 28 | +| 417 | 0 | 408 | 34 | ## Client diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index 7c7df2c2ef1e9..ea9c347652eef 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -202,7 +202,7 @@ "APMPluginSetupDependencies", ") => { config$: ", "Observable", - "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", + "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; telemetryCollectionEnabled: boolean; metricsInterval: number; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; forceSyntheticSource: boolean; }>>; getApmIndices: () => Promise>; createApmEventClient: ({ request, context, debug, }: { debug?: boolean | undefined; request: ", { @@ -431,7 +431,7 @@ "label": "config", "description": [], "signature": [ - "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", + "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; readonly telemetryCollectionEnabled: boolean; readonly metricsInterval: number; readonly agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; readonly forceSyntheticSource: boolean; }" ], @@ -823,7 +823,7 @@ "label": "APMConfig", "description": [], "signature": [ - "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", + "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; sourcemap: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; readonly telemetryCollectionEnabled: boolean; readonly metricsInterval: number; readonly agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; readonly forceSyntheticSource: boolean; }" ], @@ -7264,7 +7264,7 @@ "description": [], "signature": [ "Observable", - "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", + "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; telemetryCollectionEnabled: boolean; metricsInterval: number; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; forceSyntheticSource: boolean; }>>" ], diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index fb33ac4a26637..0ad7152cc729d 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 831f5e5d75dfa..d9a9f976bdb3d 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-12-06 +date: 2022-12-08 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 5f14e199c7773..46634eef66820 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-12-06 +date: 2022-12-08 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 b4a93382f17aa..9323d5b895499 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-12-06 +date: 2022-12-08 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 e6ae7fa670928..7375db95c88a7 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-12-06 +date: 2022-12-08 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 7748405cb8dd2..824905af39658 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-12-06 +date: 2022-12-08 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 6d1072645643c..c8229342ad01f 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-12-06 +date: 2022-12-08 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 38f299744c356..f9426757c7998 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-12-06 +date: 2022-12-08 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 21ad542bcd77a..1a6e46a30000e 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-12-06 +date: 2022-12-08 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 9781902a75b67..c3b93b1ce5ddc 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-12-06 +date: 2022-12-08 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 196a3ebf739a0..d8336365a3ff3 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.devdocs.json b/api_docs/controls.devdocs.json index 5d59117226bfb..4ad1f3165c5dc 100644 --- a/api_docs/controls.devdocs.json +++ b/api_docs/controls.devdocs.json @@ -288,7 +288,39 @@ "label": "addDataControlFromField", "description": [], "signature": [ - "({ uuid, dataViewId, fieldName, title, }: { uuid?: string | undefined; dataViewId: string; fieldName: string; title?: string | undefined; }) => Promise<", + "(controlProps: ", + { + "pluginId": "controls", + "scope": "public", + "docId": "kibControlsPluginApi", + "section": "def-public.AddDataControlProps", + "text": "AddDataControlProps" + }, + ") => Promise<", + { + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.IEmbeddable", + "text": "IEmbeddable" + }, + "<", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddableInput", + "text": "EmbeddableInput" + }, + ", ", + { + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.EmbeddableOutput", + "text": "EmbeddableOutput" + }, + ", any> | ", { "pluginId": "embeddable", "scope": "public", @@ -296,7 +328,53 @@ "section": "def-public.ErrorEmbeddable", "text": "ErrorEmbeddable" }, - " | ", + ">" + ], + "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "controls", + "id": "def-public.ControlGroupContainer.addDataControlFromField.$1", + "type": "Object", + "tags": [], + "label": "controlProps", + "description": [], + "signature": [ + { + "pluginId": "controls", + "scope": "public", + "docId": "kibControlsPluginApi", + "section": "def-public.AddDataControlProps", + "text": "AddDataControlProps" + } + ], + "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "controls", + "id": "def-public.ControlGroupContainer.addOptionsListControl", + "type": "Function", + "tags": [], + "label": "addOptionsListControl", + "description": [], + "signature": [ + "(controlProps: ", + { + "pluginId": "controls", + "scope": "public", + "docId": "kibControlsPluginApi", + "section": "def-public.AddOptionsListControlProps", + "text": "AddOptionsListControlProps" + }, + ") => Promise<", { "pluginId": "embeddable", "scope": "public", @@ -304,57 +382,87 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "<{ viewMode: ", + "<", { "pluginId": "embeddable", "scope": "common", "docId": "kibEmbeddablePluginApi", - "section": "def-common.ViewMode", - "text": "ViewMode" + "section": "def-common.EmbeddableInput", + "text": "EmbeddableInput" }, - "; title: string; id: string; lastReloadRequestTime: number; hidePanelTitles: boolean; enhancements: ", + ", ", { - "pluginId": "@kbn/utility-types", - "scope": "server", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-server.SerializableRecord", - "text": "SerializableRecord" + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.EmbeddableOutput", + "text": "EmbeddableOutput" }, - "; disabledActions: string[]; disableTriggers: boolean; searchSessionId: string; syncColors: boolean; syncCursor: boolean; syncTooltips: boolean; executionContext: ", + ", any> | ", { - "pluginId": "@kbn/core-execution-context-common", - "scope": "common", - "docId": "kibKbnCoreExecutionContextCommonPluginApi", - "section": "def-common.KibanaExecutionContext", - "text": "KibanaExecutionContext" + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.ErrorEmbeddable", + "text": "ErrorEmbeddable" }, - "; query: ", + ">" + ], + "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - "; filters: ", + "parentPluginId": "controls", + "id": "def-public.ControlGroupContainer.addOptionsListControl.$1", + "type": "CompoundType", + "tags": [], + "label": "controlProps", + "description": [], + "signature": [ + { + "pluginId": "controls", + "scope": "public", + "docId": "kibControlsPluginApi", + "section": "def-public.AddOptionsListControlProps", + "text": "AddOptionsListControlProps" + } + ], + "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "controls", + "id": "def-public.ControlGroupContainer.addRangeSliderControl", + "type": "Function", + "tags": [], + "label": "addRangeSliderControl", + "description": [], + "signature": [ + "(controlProps: ", + "AddRangeSliderControlProps", + ") => Promise<", { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.IEmbeddable", + "text": "IEmbeddable" }, - "[]; timeRange: ", + "<", { - "pluginId": "@kbn/es-query", + "pluginId": "embeddable", "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddableInput", + "text": "EmbeddableInput" }, - "; timeslice: [number, number]; controlStyle: \"twoLine\" | \"oneLine\"; ignoreParentSettings: ", - "ParentIgnoreSettings", - "; fieldName: string; parentFieldName: string; childFieldName: string; dataViewId: string; }, ", + ", ", { "pluginId": "embeddable", "scope": "public", @@ -362,7 +470,15 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ", any>>" + ", any> | ", + { + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.ErrorEmbeddable", + "text": "ErrorEmbeddable" + }, + ">" ], "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", "deprecated": false, @@ -370,70 +486,70 @@ "children": [ { "parentPluginId": "controls", - "id": "def-public.ControlGroupContainer.addDataControlFromField.$1", - "type": "Object", + "id": "def-public.ControlGroupContainer.addRangeSliderControl.$1", + "type": "CompoundType", "tags": [], - "label": "{\n uuid,\n dataViewId,\n fieldName,\n title,\n }", + "label": "controlProps", "description": [], + "signature": [ + "AddRangeSliderControlProps" + ], "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "controls", - "id": "def-public.ControlGroupContainer.addDataControlFromField.$1.uuid", - "type": "string", - "tags": [], - "label": "uuid", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "controls", - "id": "def-public.ControlGroupContainer.addDataControlFromField.$1.dataViewId", - "type": "string", - "tags": [], - "label": "dataViewId", - "description": [], - "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "controls", - "id": "def-public.ControlGroupContainer.addDataControlFromField.$1.fieldName", - "type": "string", - "tags": [], - "label": "fieldName", - "description": [], - "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "controls", - "id": "def-public.ControlGroupContainer.addDataControlFromField.$1.title", - "type": "string", - "tags": [], - "label": "title", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", - "deprecated": false, - "trackAdoption": false - } - ] + "isRequired": true } ], "returnComment": [] }, + { + "parentPluginId": "controls", + "id": "def-public.ControlGroupContainer.addTimeSliderControl", + "type": "Function", + "tags": [], + "label": "addTimeSliderControl", + "description": [], + "signature": [ + "() => Promise<", + { + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.IEmbeddable", + "text": "IEmbeddable" + }, + "<", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.EmbeddableInput", + "text": "EmbeddableInput" + }, + ", ", + { + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.EmbeddableOutput", + "text": "EmbeddableOutput" + }, + ", any> | ", + { + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.ErrorEmbeddable", + "text": "ErrorEmbeddable" + }, + ">" + ], + "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "controls", "id": "def-public.ControlGroupContainer.getCreateControlButton", @@ -4453,7 +4569,15 @@ "section": "def-public.AddDataControlProps", "text": "AddDataControlProps" }, - " & { selectedOptions?: string[] | undefined; }" + " & Partial<", + { + "pluginId": "controls", + "scope": "common", + "docId": "kibControlsPluginApi", + "section": "def-common.OptionsListEmbeddableInput", + "text": "OptionsListEmbeddableInput" + }, + ">" ], "path": "src/plugins/controls/public/control_group/control_group_input_builder.ts", "deprecated": false, diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index fa1bcf776d4d6..f2e5778c55b35 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-prese | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 267 | 0 | 258 | 10 | +| 268 | 0 | 259 | 10 | ## Client diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index a8e106fcb7b8b..c10a629fb75c7 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -13186,11 +13186,11 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" + "path": "x-pack/plugins/alerting/server/rules_client/common/inject_references.ts" }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" + "path": "x-pack/plugins/alerting/server/rules_client/common/inject_references.ts" }, { "plugin": "alerting", @@ -13218,11 +13218,11 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts" + "path": "x-pack/plugins/alerting/server/types.ts" }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts" + "path": "x-pack/plugins/alerting/server/types.ts" }, { "plugin": "canvas", @@ -17081,6 +17081,29 @@ "path": "src/plugins/discover/server/ui_settings.ts" } ] + }, + { + "parentPluginId": "core", + "id": "def-public.UiSettingsParams.scope", + "type": "CompoundType", + "tags": [], + "label": "scope", + "description": [ + "\nScope of the setting. `Global` denotes a setting globally available across namespaces. `Namespace` denotes a setting\nscoped to a namespace. The default value is 'namespace'" + ], + "signature": [ + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsScope", + "text": "UiSettingsScope" + }, + " | undefined" + ], + "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -18247,7 +18270,15 @@ "section": "def-common.DeprecationSettings", "text": "DeprecationSettings" }, - " | undefined; order?: number | undefined; metric?: { type: string; name: string; } | undefined; }" + " | undefined; order?: number | undefined; metric?: { type: string; name: string; } | undefined; scope?: ", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsScope", + "text": "UiSettingsScope" + }, + " | undefined; }" ], "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", "deprecated": false, @@ -49019,11 +49050,11 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" + "path": "x-pack/plugins/alerting/server/rules_client/common/inject_references.ts" }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" + "path": "x-pack/plugins/alerting/server/rules_client/common/inject_references.ts" }, { "plugin": "alerting", @@ -49051,11 +49082,11 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts" + "path": "x-pack/plugins/alerting/server/types.ts" }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts" + "path": "x-pack/plugins/alerting/server/types.ts" }, { "plugin": "canvas", @@ -59390,6 +59421,29 @@ "path": "src/plugins/discover/server/ui_settings.ts" } ] + }, + { + "parentPluginId": "core", + "id": "def-server.UiSettingsParams.scope", + "type": "CompoundType", + "tags": [], + "label": "scope", + "description": [ + "\nScope of the setting. `Global` denotes a setting globally available across namespaces. `Namespace` denotes a setting\nscoped to a namespace. The default value is 'namespace'" + ], + "signature": [ + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsScope", + "text": "UiSettingsScope" + }, + " | undefined" + ], + "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -64088,7 +64142,15 @@ "section": "def-common.DeprecationSettings", "text": "DeprecationSettings" }, - " | undefined; order?: number | undefined; metric?: { type: string; name: string; } | undefined; }" + " | undefined; order?: number | undefined; metric?: { type: string; name: string; } | undefined; scope?: ", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsScope", + "text": "UiSettingsScope" + }, + " | undefined; }" ], "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", "deprecated": false, diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 2c82717588c5d..df3d0f9fa292c 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-12-06 +date: 2022-12-08 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 | |-------------------|-----------|------------------------|-----------------| -| 2796 | 17 | 1007 | 0 | +| 2798 | 17 | 1007 | 0 | ## Client diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index cd6737258ea79..7f901267553c7 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-12-06 +date: 2022-12-08 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 545c36050f22c..84b80a1e93082 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-12-06 +date: 2022-12-08 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 9168f7f6429fd..1dd5b067eb602 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-12-06 +date: 2022-12-08 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 49b2390727a5a..f64011e1dfda5 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -7788,6 +7788,20 @@ "path": "src/plugins/data_views/common/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-public.GetFieldsOptions.includeUnmapped", + "type": "CompoundType", + "tags": [], + "label": "includeUnmapped", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data_views/common/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -12923,15 +12937,15 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" + "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts" + "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" }, { "plugin": "securitySolution", @@ -17008,7 +17022,7 @@ "\n Get a list of field objects for an index pattern that may contain wildcards\n" ], "signature": [ - "(options: { pattern: string | string[]; metaFields?: string[] | undefined; fieldCapsOptions?: { allow_no_indices: boolean; } | undefined; type?: string | undefined; rollupIndex?: string | undefined; filter?: ", + "(options: { pattern: string | string[]; metaFields?: string[] | undefined; fieldCapsOptions?: { allow_no_indices: boolean; includeUnmapped?: boolean | undefined; } | undefined; type?: string | undefined; rollupIndex?: string | undefined; filter?: ", "QueryDslQueryContainer", " | undefined; }) => Promise<{ fields: ", { @@ -17071,7 +17085,7 @@ "label": "fieldCapsOptions", "description": [], "signature": [ - "{ allow_no_indices: boolean; } | undefined" + "{ allow_no_indices: boolean; includeUnmapped?: boolean | undefined; } | undefined" ], "path": "src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts", "deprecated": false, @@ -20625,15 +20639,15 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" + "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts" + "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" }, { "plugin": "securitySolution", @@ -26659,6 +26673,20 @@ "path": "src/plugins/data_views/common/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.GetFieldsOptions.includeUnmapped", + "type": "CompoundType", + "tags": [], + "label": "includeUnmapped", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data_views/common/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/data.mdx b/api_docs/data.mdx index d198d95574253..dfff31f5cfce7 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.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 | |-------------------|-----------|------------------------|-----------------| -| 3265 | 119 | 2553 | 27 | +| 3279 | 119 | 2561 | 27 | ## Client diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index fc0ac649b12de..2ff0f0fe9ec2f 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.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 | |-------------------|-----------|------------------------|-----------------| -| 3265 | 119 | 2553 | 27 | +| 3279 | 119 | 2561 | 27 | ## Client diff --git a/api_docs/data_search.devdocs.json b/api_docs/data_search.devdocs.json index 0fedf85ba91d7..ef2a95934d53a 100644 --- a/api_docs/data_search.devdocs.json +++ b/api_docs/data_search.devdocs.json @@ -15523,7 +15523,7 @@ "label": "handleRequest", "description": [], "signature": [ - "({ abortSignal, aggs, filters, indexPattern, inspectorAdapters, query, searchSessionId, searchSourceService, timeFields, timeRange, disableShardWarnings, getNow, executionContext, }: ", + "({ abortSignal, aggs, filters, indexPattern, inspectorAdapters, query, searchSessionId, searchSourceService, timeFields, timeRange, disableShardWarnings, getNow, executionContext, title, description, }: ", { "pluginId": "data", "scope": "common", @@ -15552,7 +15552,7 @@ "id": "def-common.handleRequest.$1", "type": "Object", "tags": [], - "label": "{\n abortSignal,\n aggs,\n filters,\n indexPattern,\n inspectorAdapters,\n query,\n searchSessionId,\n searchSourceService,\n timeFields,\n timeRange,\n disableShardWarnings,\n getNow,\n executionContext,\n}", + "label": "{\n abortSignal,\n aggs,\n filters,\n indexPattern,\n inspectorAdapters,\n query,\n searchSessionId,\n searchSourceService,\n timeFields,\n timeRange,\n disableShardWarnings,\n getNow,\n executionContext,\n title,\n description,\n}", "description": [], "signature": [ { @@ -15860,6 +15860,45 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "data", + "id": "def-common.isAbsoluteTimeShift", + "type": "Function", + "tags": [], + "label": "isAbsoluteTimeShift", + "description": [ + "\nCheck function to detect an absolute time shift.\nThe check is performed only on the string format and the timestamp is not validated:\nuse the validateAbsoluteTimeShift fucntion to perform more in depth checks" + ], + "signature": [ + "(val?: string | undefined) => boolean" + ], + "path": "src/plugins/data/common/search/aggs/utils/parse_time_shift.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.isAbsoluteTimeShift.$1", + "type": "string", + "tags": [], + "label": "val", + "description": [ + "the string to parse (it assumes it has been trimmed already)" + ], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/aggs/utils/parse_time_shift.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [ + "true if an absolute time shift" + ], + "initialIsOpen": false + }, { "parentPluginId": "data", "id": "def-common.isAutoInterval", @@ -16495,6 +16534,75 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "data", + "id": "def-common.parseAbsoluteTimeShift", + "type": "Function", + "tags": [], + "label": "parseAbsoluteTimeShift", + "description": [ + "\nParses an absolute time shift string and returns its equivalent duration" + ], + "signature": [ + "(val: string, timeRange: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + " | undefined) => { value: moment.Duration; reason: null; } | { value: \"invalid\"; reason: \"missingTimerange\" | \"notAbsoluteTimeShift\" | \"invalidDate\" | \"shiftAfterTimeRange\"; }" + ], + "path": "src/plugins/data/common/search/aggs/utils/parse_time_shift.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.parseAbsoluteTimeShift.$1", + "type": "string", + "tags": [], + "label": "val", + "description": [ + "the string to parse" + ], + "signature": [ + "string" + ], + "path": "src/plugins/data/common/search/aggs/utils/parse_time_shift.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "data", + "id": "def-common.parseAbsoluteTimeShift.$2", + "type": "Object", + "tags": [], + "label": "timeRange", + "description": [ + "the current date histogram interval" + ], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + " | undefined" + ], + "path": "src/plugins/data/common/search/aggs/utils/parse_time_shift.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "data", "id": "def-common.parseEsInterval", @@ -17272,6 +17380,73 @@ ], "returnComment": [], "initialIsOpen": false + }, + { + "parentPluginId": "data", + "id": "def-common.validateAbsoluteTimeShift", + "type": "Function", + "tags": [], + "label": "validateAbsoluteTimeShift", + "description": [ + "\nRelaxed version of the parsing validation\nThis version of the validation applies the timeRange validation only when passed" + ], + "signature": [ + "(val: string, timeRange: ", + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + " | undefined) => \"missingTimerange\" | \"notAbsoluteTimeShift\" | \"invalidDate\" | \"shiftAfterTimeRange\" | undefined" + ], + "path": "src/plugins/data/common/search/aggs/utils/parse_time_shift.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.validateAbsoluteTimeShift.$1", + "type": "string", + "tags": [], + "label": "val", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data/common/search/aggs/utils/parse_time_shift.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "data", + "id": "def-common.validateAbsoluteTimeShift.$2", + "type": "Object", + "tags": [], + "label": "timeRange", + "description": [], + "signature": [ + { + "pluginId": "data", + "scope": "common", + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" + }, + " | undefined" + ], + "path": "src/plugins/data/common/search/aggs/utils/parse_time_shift.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [ + "the reason id if the absolute shift is not valid, undefined otherwise" + ], + "initialIsOpen": false } ], "interfaces": [ @@ -29414,6 +29589,34 @@ "path": "src/plugins/data/common/search/expressions/esaggs/request_handler.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.RequestHandlerParams.title", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/expressions/esaggs/request_handler.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.RequestHandlerParams.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/data/common/search/expressions/esaggs/request_handler.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -34912,6 +35115,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "data", + "id": "def-common.REASON_ID_TYPES", + "type": "Type", + "tags": [], + "label": "REASON_ID_TYPES", + "description": [], + "signature": [ + "\"missingTimerange\" | \"notAbsoluteTimeShift\" | \"invalidDate\" | \"shiftAfterTimeRange\"" + ], + "path": "src/plugins/data/common/search/aggs/utils/parse_time_shift.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "data", "id": "def-common.SAMPLER_AGG_NAME", @@ -40784,6 +41002,21 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "data", + "id": "def-common.REASON_IDS", + "type": "Object", + "tags": [], + "label": "REASON_IDS", + "description": [], + "signature": [ + "{ readonly missingTimerange: \"missingTimerange\"; readonly notAbsoluteTimeShift: \"notAbsoluteTimeShift\"; readonly invalidDate: \"invalidDate\"; readonly shiftAfterTimeRange: \"shiftAfterTimeRange\"; }" + ], + "path": "src/plugins/data/common/search/aggs/utils/parse_time_shift.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "data", "id": "def-common.removeFilterFunction", diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 15b32b4c7d379..3a87348fe124a 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.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 | |-------------------|-----------|------------------------|-----------------| -| 3265 | 119 | 2553 | 27 | +| 3279 | 119 | 2561 | 27 | ## Client diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 08c21e497f700..ea203222a4424 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-12-06 +date: 2022-12-08 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 1b388650a7386..41df9c22d43b5 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-12-06 +date: 2022-12-08 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 4328a1d062a1d..db6554be3585b 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-12-06 +date: 2022-12-08 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 eccf7c5366fa6..608e1f45bd5b0 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -77,15 +77,15 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" + "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts" + "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" }, { "plugin": "securitySolution", @@ -8378,15 +8378,15 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" + "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts" + "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" }, { "plugin": "securitySolution", @@ -12785,7 +12785,7 @@ "\n Get a list of field objects for an index pattern that may contain wildcards\n" ], "signature": [ - "(options: { pattern: string | string[]; metaFields?: string[] | undefined; fieldCapsOptions?: { allow_no_indices: boolean; } | undefined; type?: string | undefined; rollupIndex?: string | undefined; filter?: ", + "(options: { pattern: string | string[]; metaFields?: string[] | undefined; fieldCapsOptions?: { allow_no_indices: boolean; includeUnmapped?: boolean | undefined; } | undefined; type?: string | undefined; rollupIndex?: string | undefined; filter?: ", "QueryDslQueryContainer", " | undefined; }) => Promise<{ fields: ", { @@ -12848,7 +12848,7 @@ "label": "fieldCapsOptions", "description": [], "signature": [ - "{ allow_no_indices: boolean; } | undefined" + "{ allow_no_indices: boolean; includeUnmapped?: boolean | undefined; } | undefined" ], "path": "src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts", "deprecated": false, @@ -15760,15 +15760,15 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" + "path": "x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts" + "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts" }, { "plugin": "securitySolution", @@ -23434,6 +23434,20 @@ "path": "src/plugins/data_views/common/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "dataViews", + "id": "def-common.GetFieldsOptions.includeUnmapped", + "type": "CompoundType", + "tags": [], + "label": "includeUnmapped", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/data_views/common/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index e399aabd710a7..7b04b1cc7b9e9 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1021 | 0 | 227 | 2 | +| 1022 | 0 | 228 | 2 | ## Client diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index afb22249768d9..79c9c406ff282 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-12-06 +date: 2022-12-08 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 1f8ba6e1aa122..1e76211a95f1f 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 8ca8a5c288905..0a911e9c26b22 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -847,12 +847,12 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts#:~:text=create) | - | | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [dependencies_start_mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts#:~:text=indexPatterns) | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx#:~:text=title)+ 18 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx#:~:text=title)+ 18 more | - | | | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts#:~:text=create) | - | | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts#:~:text=options) | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx#:~:text=title)+ 18 more | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx#:~:text=title)+ 4 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx#:~:text=title)+ 18 more | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx#:~:text=title)+ 4 more | - | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts#:~:text=license%24) | 8.8.0 | @@ -864,7 +864,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [manifest_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [manifest_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID)+ 34 more | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION) | - | -| | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/utils.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/utils.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [service_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [service_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [service_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID)+ 32 more | - | +| | [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/utils.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/utils.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [service_actions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID)+ 32 more | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [manifest_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [manifest_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID)+ 16 more | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 800c6ecd38273..f5f9255595d74 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 6cc8a050865da..4997b8204e56e 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-12-06 +date: 2022-12-08 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 58335dd854878..95f49388264c4 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-12-06 +date: 2022-12-08 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 3faa50733265a..7eda85a7439f1 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index 359f01ac6cc60..f276b7806e715 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -1277,7 +1277,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(type: string, explicitInput: Partial) => Promise>(type: string, explicitInput: Partial) => Promise<", { "pluginId": "embeddable", "scope": "public", @@ -1285,7 +1285,7 @@ "section": "def-public.ErrorEmbeddable", "text": "ErrorEmbeddable" }, - ">" + " | E>" ], "path": "src/plugins/embeddable/public/lib/containers/container.ts", "deprecated": false, @@ -1702,7 +1702,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ", any>>(id: string) => Promise<", + ", any>>(id: string) => Promise" + ">" ], "path": "src/plugins/embeddable/public/lib/containers/container.ts", "deprecated": false, @@ -1941,6 +1941,124 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "embeddable", + "id": "def-public.Container.createAndSaveEmbeddable", + "type": "Function", + "tags": [], + "label": "createAndSaveEmbeddable", + "description": [], + "signature": [ + " = ", + { + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.IEmbeddable", + "text": "IEmbeddable" + }, + ">(type: string, panelState: ", + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.PanelState", + "text": "PanelState" + }, + "<{ id: string; }>) => Promise" + ], + "path": "src/plugins/embeddable/public/lib/containers/container.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "embeddable", + "id": "def-public.Container.createAndSaveEmbeddable.$1", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/embeddable/public/lib/containers/container.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "embeddable", + "id": "def-public.Container.createAndSaveEmbeddable.$2", + "type": "Object", + "tags": [], + "label": "panelState", + "description": [], + "signature": [ + { + "pluginId": "embeddable", + "scope": "common", + "docId": "kibEmbeddablePluginApi", + "section": "def-common.PanelState", + "text": "PanelState" + }, + "<{ id: string; }>" + ], + "path": "src/plugins/embeddable/public/lib/containers/container.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index b972c0f166e76..5b767fe8b4187 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 510 | 6 | 410 | 4 | +| 513 | 6 | 413 | 4 | ## Client diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 0f08c9373088e..25d420842d1ff 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-12-06 +date: 2022-12-08 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 1c25d847b99e2..ca03db1187090 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-12-06 +date: 2022-12-08 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 51ab2b0bbcc61..f2a833aef2e74 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-12-06 +date: 2022-12-08 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 40e1fd571a8d7..62a4a1b1e287c 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-12-06 +date: 2022-12-08 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 4ea55ef4ad838..dd92bcb0a74a1 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.devdocs.json b/api_docs/event_log.devdocs.json index d25c528e84274..a9d6c887f136e 100644 --- a/api_docs/event_log.devdocs.json +++ b/api_docs/event_log.devdocs.json @@ -1499,7 +1499,7 @@ "label": "data", "description": [], "signature": [ - "(Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; reason?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; kind?: string | undefined; hash?: string | undefined; url?: string | undefined; code?: string | undefined; action?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined)[]" + "(Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; reason?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; kind?: string | undefined; hash?: string | undefined; url?: string | undefined; code?: string | undefined; action?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined)[]" ], "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", "deprecated": false, @@ -1519,7 +1519,7 @@ "label": "IEvent", "description": [], "signature": [ - "DeepPartial | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; reason?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; kind?: string | undefined; hash?: string | undefined; url?: string | undefined; code?: string | undefined; action?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}>>> | undefined" + "DeepPartial | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; reason?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; kind?: string | undefined; hash?: string | undefined; url?: string | undefined; code?: string | undefined; action?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}>>> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, @@ -1534,7 +1534,7 @@ "label": "IValidatedEvent", "description": [], "signature": [ - "Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; reason?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; kind?: string | undefined; hash?: string | undefined; url?: string | undefined; code?: string | undefined; action?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined" + "Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; flapping?: boolean | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; reason?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; kind?: string | undefined; hash?: string | undefined; url?: string | undefined; code?: string | undefined; action?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 47e36c70b1092..7b747091962a3 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-12-06 +date: 2022-12-08 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 c98c67e26ebd5..489e173a2b136 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-12-06 +date: 2022-12-08 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 54980395d540a..ae4798b60f640 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-12-06 +date: 2022-12-08 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 2ef98aa58496f..cb965b196f616 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-12-06 +date: 2022-12-08 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 17756263a9a36..ef3c7059f8161 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-12-06 +date: 2022-12-08 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 4703bf686e102..a9fa21973dcba 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-12-06 +date: 2022-12-08 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 56f4a0d5ffa0d..5793f6737c28a 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-12-06 +date: 2022-12-08 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 4540e673800ac..166da3294eda5 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-12-06 +date: 2022-12-08 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 29e876af2e085..c8136d7cc726c 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-12-06 +date: 2022-12-08 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 5cb0fdc2e8d90..4080f6cf922fc 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-12-06 +date: 2022-12-08 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 25a9194b7fdc4..c5b45fc137775 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-12-06 +date: 2022-12-08 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 47f8ce5c9c3f7..3f4f7811e027d 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-12-06 +date: 2022-12-08 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 0eb8c9e8d98de..cef1299eea518 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-12-06 +date: 2022-12-08 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 fa79f4d4769e1..0663c5d8fe527 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-12-06 +date: 2022-12-08 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 c412c816d8d47..5cc82207ee658 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-12-06 +date: 2022-12-08 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 d7499f356373c..eeca055c1e64f 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-12-06 +date: 2022-12-08 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 065653ee0f558..4492421da917c 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-12-06 +date: 2022-12-08 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 0f649447b182c..52946eccbcef5 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 4e63b994fb9d2..9abd88a680f6c 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/files_management.mdx b/api_docs/files_management.mdx index 334f957e480d1..1aa0cabf504d4 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'filesManagement'] --- import filesManagementObj from './files_management.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index ce10a6d3a887a..cdce3a06412cc 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -12744,7 +12744,7 @@ "label": "data_stream", "description": [], "signature": [ - "{ dataset: string; type: string; elasticsearch?: { privileges?: { indices?: string[] | undefined; } | undefined; index_mode?: string | undefined; } | undefined; }" + "{ dataset: string; type: string; elasticsearch?: { privileges?: { indices?: string[] | undefined; } | undefined; index_mode?: string | undefined; source_mode?: string | undefined; } | undefined; }" ], "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", "deprecated": false, diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 1b92c59893390..b24563dea1581 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-12-06 +date: 2022-12-08 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 9ab3b37ee0964..ec0ccf8b12af0 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-12-06 +date: 2022-12-08 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 b4e513b13febe..b08543ec0d75b 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-12-06 +date: 2022-12-08 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 7c88b4c2d9494..638479010fa59 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-12-06 +date: 2022-12-08 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 beaa6e117d095..ae25ff15eccc1 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-12-06 +date: 2022-12-08 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 6130a5f4a0b2b..7b08a70b4eaea 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-12-06 +date: 2022-12-08 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 d3bf04c9aa359..6bbd69c28823e 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-12-06 +date: 2022-12-08 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 dd02144506fac..82563e09f4095 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-12-06 +date: 2022-12-08 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 6a5f7832fac3d..c48b692006c0b 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-12-06 +date: 2022-12-08 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 c425c4cbc8c2f..0e1c932cc1e45 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-12-06 +date: 2022-12-08 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 d4a709746abc1..1b4cabbb7232b 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-12-06 +date: 2022-12-08 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 90f1047c1ff4f..48f8e63f68d37 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-12-06 +date: 2022-12-08 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 65beb5bc8ec9a..ff9e015817321 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-12-06 +date: 2022-12-08 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 d4117d431d084..73a2882b72aa0 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 1257c600b388f..2f24eb7cdf1a6 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index acc0e843ccbf6..a141da72d50df 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 6a61e0d734e90..c99e7401f7f88 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index eede385a167f7..2caff35b86ae3 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index f05383c9823c9..0575003700785 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index 311054668f3ac..6af9fa3901884 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 90b5fea1d2130..1bfe4eb331f94 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-12-06 +date: 2022-12-08 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.devdocs.json b/api_docs/kbn_apm_synthtrace.devdocs.json index c2a9713da0caf..c91af4fa61088 100644 --- a/api_docs/kbn_apm_synthtrace.devdocs.json +++ b/api_docs/kbn_apm_synthtrace.devdocs.json @@ -1062,7 +1062,9 @@ "section": "def-server.Fields", "text": "Fields" }, - " & Partial<{ 'timestamp.us'?: number | undefined; 'agent.name': string; 'agent.version': string; 'container.id': string; 'destination.address': string; 'destination.port': number; 'ecs.version': string; 'event.outcome': string; 'event.ingested': number; 'error.id': string; 'error.exception': ", + " & Partial<{ 'timestamp.us'?: number | undefined; 'agent.name': string; 'agent.version': string; 'client.geo.city_name': string; 'client.geo.continent_name': string; 'client.geo.country_iso_code': string; 'client.geo.country_name': string; 'client.geo.region_iso_code': string; 'client.geo.region_name': string; 'client.geo.location': ", + "GeoLocation", + "; 'client.ip': string; 'cloud.provider': string; 'cloud.project.name': string; 'cloud.service.name': string; 'cloud.availability_zone': string; 'cloud.machine.type': string; 'cloud.region': string; 'container.id': string; 'destination.address': string; 'destination.port': number; 'device.id': string; 'device.model.identifier': string; 'device.model.name': string; 'device.manufacturer': string; 'ecs.version': string; 'event.outcome': string; 'event.ingested': number; 'error.id': string; 'error.exception': ", { "pluginId": "@kbn/apm-synthtrace", "scope": "server", @@ -1070,9 +1072,9 @@ "section": "def-server.ApmException", "text": "ApmException" }, - "[]; 'error.grouping_name': string; 'error.grouping_key': string; 'host.name': string; 'host.architecture': string; 'host.hostname': string; 'http.request.method': string; 'http.response.status_code': number; 'kubernetes.pod.uid': string; 'kubernetes.pod.name': string; 'metricset.name': string; observer: ", + "[]; 'error.grouping_name': string; 'error.grouping_key': string; 'faas.id': string; 'faas.name': string; 'faas.coldstart': boolean; 'faas.execution': string; 'faas.trigger.type': string; 'faas.trigger.request_id': string; 'host.name': string; 'host.architecture': string; 'host.hostname': string; 'host.os.full': string; 'host.os.name': string; 'host.os.platform': string; 'host.os.type': string; 'host.os.version': string; 'http.request.method': string; 'http.response.status_code': number; 'kubernetes.pod.uid': string; 'kubernetes.pod.name': string; 'metricset.name': string; observer: ", "Observer", - "; 'parent.id': string; 'processor.event': string; 'processor.name': string; 'trace.id': string; 'transaction.name': string; 'transaction.type': string; 'transaction.id': string; 'transaction.duration.us': number; 'transaction.duration.histogram': { values: number[]; counts: number[]; }; 'transaction.sampled': true; 'service.name': string; 'service.version': string; 'service.environment': string; 'service.language.name': string; 'service.node.name': string; 'service.runtime.name': string; 'service.runtime.version': string; 'service.framework.name': string; 'service.target.name': string; 'service.target.type': string; 'span.action': string; 'span.id': string; 'span.name': string; 'span.type': string; 'span.subtype': string; 'span.duration.us': number; 'span.destination.service.resource': string; 'span.destination.service.response_time.sum.us': number; 'span.destination.service.response_time.count': number; 'span.self_time.count': number; 'span.self_time.sum.us': number; 'span.links': { trace: { id: string; }; span: { id: string; }; }[]; 'cloud.provider': string; 'cloud.project.name': string; 'cloud.service.name': string; 'cloud.availability_zone': string; 'cloud.machine.type': string; 'cloud.region': string; 'host.os.platform': string; 'faas.id': string; 'faas.name': string; 'faas.coldstart': boolean; 'faas.execution': string; 'faas.trigger.type': string; 'faas.trigger.request_id': string; }> & Partial<{ 'system.process.memory.size': number; 'system.memory.actual.free': number; 'system.memory.total': number; 'system.cpu.total.norm.pct': number; 'system.process.memory.rss.bytes': number; 'system.process.cpu.total.norm.pct': number; 'jvm.memory.heap.used': number; 'jvm.memory.non_heap.used': number; 'jvm.thread.count': number; 'faas.billed_duration': number; 'faas.timeout': number; 'faas.coldstart_duration': number; 'faas.duration': number; }>" + "; 'network.connection.type': string; 'network.connection.subtype': string; 'network.carrier.name': string; 'network.carrier.mcc': string; 'network.carrier.mnc': string; 'network.carrier.icc': string; 'parent.id': string; 'processor.event': string; 'processor.name': string; 'session.id': string; 'trace.id': string; 'transaction.name': string; 'transaction.type': string; 'transaction.id': string; 'transaction.duration.us': number; 'transaction.duration.histogram': { values: number[]; counts: number[]; }; 'transaction.sampled': true; 'service.name': string; 'service.version': string; 'service.environment': string; 'service.language.name': string; 'service.node.name': string; 'service.runtime.name': string; 'service.runtime.version': string; 'service.framework.name': string; 'service.framework.version': string; 'service.target.name': string; 'service.target.type': string; 'span.action': string; 'span.id': string; 'span.name': string; 'span.type': string; 'span.subtype': string; 'span.duration.us': number; 'span.destination.service.resource': string; 'span.destination.service.response_time.sum.us': number; 'span.destination.service.response_time.count': number; 'span.self_time.count': number; 'span.self_time.sum.us': number; 'span.links': { trace: { id: string; }; span: { id: string; }; }[]; 'url.original': string; }> & Partial<{ 'system.process.memory.size': number; 'system.memory.actual.free': number; 'system.memory.total': number; 'system.cpu.total.norm.pct': number; 'system.process.memory.rss.bytes': number; 'system.process.cpu.total.norm.pct': number; 'jvm.memory.heap.used': number; 'jvm.memory.non_heap.used': number; 'jvm.thread.count': number; 'faas.billed_duration': number; 'faas.timeout': number; 'faas.coldstart_duration': number; 'faas.duration': number; }>" ], "path": "packages/kbn-apm-synthtrace/src/lib/apm/apm_fields.ts", "deprecated": false, @@ -1110,6 +1112,24 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/apm-synthtrace", + "id": "def-server.apm.mobileApp", + "type": "Function", + "tags": [], + "label": "mobileApp", + "description": [], + "signature": [ + "{ (name: string, environment: string, agentName: MobileAgentName): ", + "MobileApp", + "; (options: { name: string; environment: string; agentName: MobileAgentName; }): ", + "MobileApp", + "; }" + ], + "path": "packages/kbn-apm-synthtrace/src/lib/apm/index.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/apm-synthtrace", "id": "def-server.apm.browser", @@ -1158,7 +1178,9 @@ "section": "def-server.ApmFields", "text": "ApmFields" }, - "[]) => { 'metricset.name': string; 'transaction.duration.histogram': { values: number[]; counts: number[]; }; _doc_count: number; '@timestamp'?: number | undefined; 'timestamp.us'?: number | undefined; 'agent.name'?: string | undefined; 'agent.version'?: string | undefined; 'container.id'?: string | undefined; 'destination.address'?: string | undefined; 'destination.port'?: number | undefined; 'ecs.version'?: string | undefined; 'event.outcome'?: string | undefined; 'event.ingested'?: number | undefined; 'error.id'?: string | undefined; 'error.exception'?: ", + "[]) => { 'metricset.name': string; 'transaction.duration.histogram': { values: number[]; counts: number[]; }; _doc_count: number; '@timestamp'?: number | undefined; 'timestamp.us'?: number | undefined; 'agent.name'?: string | undefined; 'agent.version'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.location'?: ", + "GeoLocation", + " | undefined; 'client.ip'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.region'?: string | undefined; 'container.id'?: string | undefined; 'destination.address'?: string | undefined; 'destination.port'?: number | undefined; 'device.id'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'device.manufacturer'?: string | undefined; 'ecs.version'?: string | undefined; 'event.outcome'?: string | undefined; 'event.ingested'?: number | undefined; 'error.id'?: string | undefined; 'error.exception'?: ", { "pluginId": "@kbn/apm-synthtrace", "scope": "server", @@ -1166,9 +1188,9 @@ "section": "def-server.ApmException", "text": "ApmException" }, - "[] | undefined; 'error.grouping_name'?: string | undefined; 'error.grouping_key'?: string | undefined; 'host.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.hostname'?: string | undefined; 'http.request.method'?: string | undefined; 'http.response.status_code'?: number | undefined; 'kubernetes.pod.uid'?: string | undefined; 'kubernetes.pod.name'?: string | undefined; observer?: ", + "[] | undefined; 'error.grouping_name'?: string | undefined; 'error.grouping_key'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.trigger.type'?: string | undefined; 'faas.trigger.request_id'?: string | undefined; 'host.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.hostname'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'http.request.method'?: string | undefined; 'http.response.status_code'?: number | undefined; 'kubernetes.pod.uid'?: string | undefined; 'kubernetes.pod.name'?: string | undefined; observer?: ", "Observer", - " | undefined; 'parent.id'?: string | undefined; 'processor.event'?: string | undefined; 'processor.name'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; 'transaction.id'?: string | undefined; 'transaction.duration.us'?: number | undefined; 'transaction.sampled'?: true | undefined; 'service.name'?: string | undefined; 'service.version'?: string | undefined; 'service.environment'?: string | undefined; 'service.language.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.runtime.name'?: string | undefined; 'service.runtime.version'?: string | undefined; 'service.framework.name'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.type'?: string | undefined; 'span.action'?: string | undefined; 'span.id'?: string | undefined; 'span.name'?: string | undefined; 'span.type'?: string | undefined; 'span.subtype'?: string | undefined; 'span.duration.us'?: number | undefined; 'span.destination.service.resource'?: string | undefined; 'span.destination.service.response_time.sum.us'?: number | undefined; 'span.destination.service.response_time.count'?: number | undefined; 'span.self_time.count'?: number | undefined; 'span.self_time.sum.us'?: number | undefined; 'span.links'?: { trace: { id: string; }; span: { id: string; }; }[] | undefined; 'cloud.provider'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.region'?: string | undefined; 'host.os.platform'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.trigger.type'?: string | undefined; 'faas.trigger.request_id'?: string | undefined; 'system.process.memory.size'?: number | undefined; 'system.memory.actual.free'?: number | undefined; 'system.memory.total'?: number | undefined; 'system.cpu.total.norm.pct'?: number | undefined; 'system.process.memory.rss.bytes'?: number | undefined; 'system.process.cpu.total.norm.pct'?: number | undefined; 'jvm.memory.heap.used'?: number | undefined; 'jvm.memory.non_heap.used'?: number | undefined; 'jvm.thread.count'?: number | undefined; 'faas.billed_duration'?: number | undefined; 'faas.timeout'?: number | undefined; 'faas.coldstart_duration'?: number | undefined; 'faas.duration'?: number | undefined; }[]" + " | undefined; 'network.connection.type'?: string | undefined; 'network.connection.subtype'?: string | undefined; 'network.carrier.name'?: string | undefined; 'network.carrier.mcc'?: string | undefined; 'network.carrier.mnc'?: string | undefined; 'network.carrier.icc'?: string | undefined; 'parent.id'?: string | undefined; 'processor.event'?: string | undefined; 'processor.name'?: string | undefined; 'session.id'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; 'transaction.id'?: string | undefined; 'transaction.duration.us'?: number | undefined; 'transaction.sampled'?: true | undefined; 'service.name'?: string | undefined; 'service.version'?: string | undefined; 'service.environment'?: string | undefined; 'service.language.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.runtime.name'?: string | undefined; 'service.runtime.version'?: string | undefined; 'service.framework.name'?: string | undefined; 'service.framework.version'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.type'?: string | undefined; 'span.action'?: string | undefined; 'span.id'?: string | undefined; 'span.name'?: string | undefined; 'span.type'?: string | undefined; 'span.subtype'?: string | undefined; 'span.duration.us'?: number | undefined; 'span.destination.service.resource'?: string | undefined; 'span.destination.service.response_time.sum.us'?: number | undefined; 'span.destination.service.response_time.count'?: number | undefined; 'span.self_time.count'?: number | undefined; 'span.self_time.sum.us'?: number | undefined; 'span.links'?: { trace: { id: string; }; span: { id: string; }; }[] | undefined; 'url.original'?: string | undefined; 'system.process.memory.size'?: number | undefined; 'system.memory.actual.free'?: number | undefined; 'system.memory.total'?: number | undefined; 'system.cpu.total.norm.pct'?: number | undefined; 'system.process.memory.rss.bytes'?: number | undefined; 'system.process.cpu.total.norm.pct'?: number | undefined; 'jvm.memory.heap.used'?: number | undefined; 'jvm.memory.non_heap.used'?: number | undefined; 'jvm.thread.count'?: number | undefined; 'faas.billed_duration'?: number | undefined; 'faas.timeout'?: number | undefined; 'faas.coldstart_duration'?: number | undefined; 'faas.duration'?: number | undefined; }[]" ], "path": "packages/kbn-apm-synthtrace/src/lib/apm/index.ts", "deprecated": false, @@ -1214,7 +1236,9 @@ "section": "def-server.ApmFields", "text": "ApmFields" }, - "[]) => { \"metricset.name\": string; 'span.destination.service.response_time.sum.us': number; 'span.destination.service.response_time.count': number; '@timestamp'?: number | undefined; 'timestamp.us'?: number | undefined; 'agent.name'?: string | undefined; 'agent.version'?: string | undefined; 'container.id'?: string | undefined; 'destination.address'?: string | undefined; 'destination.port'?: number | undefined; 'ecs.version'?: string | undefined; 'event.outcome'?: string | undefined; 'event.ingested'?: number | undefined; 'error.id'?: string | undefined; 'error.exception'?: ", + "[]) => { \"metricset.name\": string; 'span.destination.service.response_time.sum.us': number; 'span.destination.service.response_time.count': number; '@timestamp'?: number | undefined; 'timestamp.us'?: number | undefined; 'agent.name'?: string | undefined; 'agent.version'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.location'?: ", + "GeoLocation", + " | undefined; 'client.ip'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.region'?: string | undefined; 'container.id'?: string | undefined; 'destination.address'?: string | undefined; 'destination.port'?: number | undefined; 'device.id'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'device.manufacturer'?: string | undefined; 'ecs.version'?: string | undefined; 'event.outcome'?: string | undefined; 'event.ingested'?: number | undefined; 'error.id'?: string | undefined; 'error.exception'?: ", { "pluginId": "@kbn/apm-synthtrace", "scope": "server", @@ -1222,9 +1246,9 @@ "section": "def-server.ApmException", "text": "ApmException" }, - "[] | undefined; 'error.grouping_name'?: string | undefined; 'error.grouping_key'?: string | undefined; 'host.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.hostname'?: string | undefined; 'http.request.method'?: string | undefined; 'http.response.status_code'?: number | undefined; 'kubernetes.pod.uid'?: string | undefined; 'kubernetes.pod.name'?: string | undefined; observer?: ", + "[] | undefined; 'error.grouping_name'?: string | undefined; 'error.grouping_key'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.trigger.type'?: string | undefined; 'faas.trigger.request_id'?: string | undefined; 'host.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.hostname'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'http.request.method'?: string | undefined; 'http.response.status_code'?: number | undefined; 'kubernetes.pod.uid'?: string | undefined; 'kubernetes.pod.name'?: string | undefined; observer?: ", "Observer", - " | undefined; 'parent.id'?: string | undefined; 'processor.event'?: string | undefined; 'processor.name'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; 'transaction.id'?: string | undefined; 'transaction.duration.us'?: number | undefined; 'transaction.duration.histogram'?: { values: number[]; counts: number[]; } | undefined; 'transaction.sampled'?: true | undefined; 'service.name'?: string | undefined; 'service.version'?: string | undefined; 'service.environment'?: string | undefined; 'service.language.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.runtime.name'?: string | undefined; 'service.runtime.version'?: string | undefined; 'service.framework.name'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.type'?: string | undefined; 'span.action'?: string | undefined; 'span.id'?: string | undefined; 'span.name'?: string | undefined; 'span.type'?: string | undefined; 'span.subtype'?: string | undefined; 'span.duration.us'?: number | undefined; 'span.destination.service.resource'?: string | undefined; 'span.self_time.count'?: number | undefined; 'span.self_time.sum.us'?: number | undefined; 'span.links'?: { trace: { id: string; }; span: { id: string; }; }[] | undefined; 'cloud.provider'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.region'?: string | undefined; 'host.os.platform'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.trigger.type'?: string | undefined; 'faas.trigger.request_id'?: string | undefined; 'system.process.memory.size'?: number | undefined; 'system.memory.actual.free'?: number | undefined; 'system.memory.total'?: number | undefined; 'system.cpu.total.norm.pct'?: number | undefined; 'system.process.memory.rss.bytes'?: number | undefined; 'system.process.cpu.total.norm.pct'?: number | undefined; 'jvm.memory.heap.used'?: number | undefined; 'jvm.memory.non_heap.used'?: number | undefined; 'jvm.thread.count'?: number | undefined; 'faas.billed_duration'?: number | undefined; 'faas.timeout'?: number | undefined; 'faas.coldstart_duration'?: number | undefined; 'faas.duration'?: number | undefined; }[]" + " | undefined; 'network.connection.type'?: string | undefined; 'network.connection.subtype'?: string | undefined; 'network.carrier.name'?: string | undefined; 'network.carrier.mcc'?: string | undefined; 'network.carrier.mnc'?: string | undefined; 'network.carrier.icc'?: string | undefined; 'parent.id'?: string | undefined; 'processor.event'?: string | undefined; 'processor.name'?: string | undefined; 'session.id'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; 'transaction.id'?: string | undefined; 'transaction.duration.us'?: number | undefined; 'transaction.duration.histogram'?: { values: number[]; counts: number[]; } | undefined; 'transaction.sampled'?: true | undefined; 'service.name'?: string | undefined; 'service.version'?: string | undefined; 'service.environment'?: string | undefined; 'service.language.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.runtime.name'?: string | undefined; 'service.runtime.version'?: string | undefined; 'service.framework.name'?: string | undefined; 'service.framework.version'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.type'?: string | undefined; 'span.action'?: string | undefined; 'span.id'?: string | undefined; 'span.name'?: string | undefined; 'span.type'?: string | undefined; 'span.subtype'?: string | undefined; 'span.duration.us'?: number | undefined; 'span.destination.service.resource'?: string | undefined; 'span.self_time.count'?: number | undefined; 'span.self_time.sum.us'?: number | undefined; 'span.links'?: { trace: { id: string; }; span: { id: string; }; }[] | undefined; 'url.original'?: string | undefined; 'system.process.memory.size'?: number | undefined; 'system.memory.actual.free'?: number | undefined; 'system.memory.total'?: number | undefined; 'system.cpu.total.norm.pct'?: number | undefined; 'system.process.memory.rss.bytes'?: number | undefined; 'system.process.cpu.total.norm.pct'?: number | undefined; 'jvm.memory.heap.used'?: number | undefined; 'jvm.memory.non_heap.used'?: number | undefined; 'jvm.thread.count'?: number | undefined; 'faas.billed_duration'?: number | undefined; 'faas.timeout'?: number | undefined; 'faas.coldstart_duration'?: number | undefined; 'faas.duration'?: number | undefined; }[]" ], "path": "packages/kbn-apm-synthtrace/src/lib/apm/index.ts", "deprecated": false, diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 9defe8f2a6888..ccedd1ba70618 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 76 | 0 | 76 | 13 | +| 77 | 0 | 77 | 15 | ## Server diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 922b0c2d40f0b..863fd37e78ca9 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-12-06 +date: 2022-12-08 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 ae640a234c3b0..3681f5a89e232 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-12-06 +date: 2022-12-08 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 6d77f5edbaaaa..d110ecd016d99 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-12-06 +date: 2022-12-08 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 01996752e5152..7b84d87c26494 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-12-06 +date: 2022-12-08 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 29892f3b08110..2408fd1b5b214 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-12-06 +date: 2022-12-08 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 04c8f83cd7f69..680ae2bfa1377 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-12-06 +date: 2022-12-08 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 a9df84629653a..c6a8fd190af73 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-12-06 +date: 2022-12-08 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 a98c5902a066f..4af90e50e3293 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-12-06 +date: 2022-12-08 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 85def9b8eb464..1cc88ccde57ee 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-12-06 +date: 2022-12-08 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 7fc9f150d8a7e..ff06356a2dac1 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-12-06 +date: 2022-12-08 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 757fa174d4af6..f9ae396315bd4 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-12-06 +date: 2022-12-08 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 0a63152fb2b34..e1cd8c3aa768c 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-12-06 +date: 2022-12-08 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 0f53318ee7f90..a8c0730274de0 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-12-06 +date: 2022-12-08 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 0413880a5a20b..9f09e0a513d89 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 5f11d8900c77f..875de818de4f3 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 0c89b30b55b82..3794293d58751 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-12-06 +date: 2022-12-08 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 e83fcc8e3b3e5..6766a269c01f6 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-12-06 +date: 2022-12-08 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 94d5e24bba39e..43f8030e3dd3f 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index a05929d4e2c23..0a02b89aeaecc 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-12-06 +date: 2022-12-08 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 d6ae00d0756d5..827b6b6f41424 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-12-06 +date: 2022-12-08 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 e5145430f88ce..2bb4635c3f8ce 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-12-06 +date: 2022-12-08 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 0991508cdcf47..ba5d25993cf3b 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-12-06 +date: 2022-12-08 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 f7a6b42eda25c..b564eae6df7f7 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-12-06 +date: 2022-12-08 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 266547c987504..55d244f74a445 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-12-06 +date: 2022-12-08 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 4d1fdfd8a2a48..acee2b4135d1d 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-12-06 +date: 2022-12-08 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 45a42aacb49f0..ede459f998342 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-12-06 +date: 2022-12-08 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 5336232039151..127b5f0931824 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-12-06 +date: 2022-12-08 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 0819c4101f6c8..6ad1a247a18a6 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-12-06 +date: 2022-12-08 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 45f98461dc187..2e04759505460 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-12-06 +date: 2022-12-08 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 a43d045650ae2..4905076a6fb10 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-12-06 +date: 2022-12-08 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 f5afda9768202..fca66f945653f 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-12-06 +date: 2022-12-08 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 e889fd243af24..fd32b1567ea49 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-12-06 +date: 2022-12-08 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 c72d04653e0af..72fd3912c1f45 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-12-06 +date: 2022-12-08 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 f5321874a5c44..8aefc1a4e5ccc 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-12-06 +date: 2022-12-08 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 47a382543300f..4a5a113c7fb90 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-12-06 +date: 2022-12-08 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 5eb2bc6d8e06f..6a3d1fb392f77 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-12-06 +date: 2022-12-08 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 1017fcbf71a44..3f87d6133765e 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-12-06 +date: 2022-12-08 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 7668cb8db8e3e..cc4a40b1d0856 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-12-06 +date: 2022-12-08 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 95cb1ab00d50d..dd27aebd8affa 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-12-06 +date: 2022-12-08 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 8be14215cb77f..f3bf9d48c79dc 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-12-06 +date: 2022-12-08 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 d77b7e99afa22..34849151df6d2 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-12-06 +date: 2022-12-08 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 a1d5b8e9656e3..45d449c391b89 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-12-06 +date: 2022-12-08 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 16d05337094ba..cc304fb6e0e49 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-12-06 +date: 2022-12-08 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 f9795671bf19a..fee356f36f657 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-12-06 +date: 2022-12-08 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 78943917d80bc..2854b4991c5c2 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-12-06 +date: 2022-12-08 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 9b2a2dfcee9c6..378fc061fb0bb 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-12-06 +date: 2022-12-08 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 bf65db30c301c..48a86f2ecc671 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-12-06 +date: 2022-12-08 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 8614904ddbfb3..78b86ea1747b7 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-12-06 +date: 2022-12-08 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 cce389eee0d61..db8d686dfd2f7 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-12-06 +date: 2022-12-08 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 3c479a5108b30..0ade7d0bedf52 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-12-06 +date: 2022-12-08 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 4ac0e5816b528..3ca423d822747 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-12-06 +date: 2022-12-08 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 fefb775e4820d..875a79b830759 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-12-06 +date: 2022-12-08 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 58198264711b2..a311169bb707d 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-12-06 +date: 2022-12-08 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 ad2679338e83d..aa829eb5af1ab 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-12-06 +date: 2022-12-08 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 84b57b013c071..e3db0cb91f69d 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-12-06 +date: 2022-12-08 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 ce531e597364f..e3ad0f4b32da0 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-12-06 +date: 2022-12-08 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 bf2ab3df3b3bb..0dbe495d2af91 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-12-06 +date: 2022-12-08 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 73d1d16382b47..ecb0528216d50 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-12-06 +date: 2022-12-08 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 ab99ccbbaac38..ad6f4f59c5cb6 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-12-06 +date: 2022-12-08 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 cc40714a7680b..427736e24ab9f 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-12-06 +date: 2022-12-08 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 d6e0440137555..20ba82f1c86e8 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-12-06 +date: 2022-12-08 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 986e9be1b12e0..fa7a0d14c3b74 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-12-06 +date: 2022-12-08 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 7d435faba134e..3d0ec76fefe5c 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-12-06 +date: 2022-12-08 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 e8ca4f852365e..5fceaa18261f5 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-12-06 +date: 2022-12-08 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 c99af5e477c49..48070b198cf09 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-12-06 +date: 2022-12-08 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 61f3b26de33e3..e3a3b2e9053e1 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-12-06 +date: 2022-12-08 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 33bed741ec8c9..105b5cc529624 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-12-06 +date: 2022-12-08 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 20b34df3790ee..9c90617eea6be 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-12-06 +date: 2022-12-08 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 c231ae64a5c22..3ae9159df376c 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-12-06 +date: 2022-12-08 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 eec495f106c97..d1e8a9a7ace0e 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-12-06 +date: 2022-12-08 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 1f42dab2dd77e..1e865809c32b0 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-12-06 +date: 2022-12-08 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 9280594a7952e..9fb8f5cdd38c9 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-12-06 +date: 2022-12-08 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 c430f2d25a6b9..344eeb3905ed6 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-12-06 +date: 2022-12-08 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 cbf061df2f53b..cbb033e64daab 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-12-06 +date: 2022-12-08 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 3a3a59cc24db1..ab53f41426d2f 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-12-06 +date: 2022-12-08 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 5bc170371eac6..f10198c7767b4 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-12-06 +date: 2022-12-08 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 e14d3e5f6e225..667d24ae601b7 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.devdocs.json b/api_docs/kbn_core_http_server_internal.devdocs.json index ac5218e06dbbe..a7be449344f0c 100644 --- a/api_docs/kbn_core_http_server_internal.devdocs.json +++ b/api_docs/kbn_core_http_server_internal.devdocs.json @@ -843,7 +843,7 @@ "label": "HttpConfigType", "description": [], "signature": [ - "{ readonly uuid?: string | undefined; readonly basePath?: string | undefined; readonly publicBaseUrl?: string | undefined; readonly name: string; readonly host: string; readonly compression: Readonly<{ referrerWhitelist?: string[] | undefined; } & { enabled: boolean; brotli: Readonly<{} & { enabled: boolean; quality: number; }>; }>; readonly ssl: Readonly<{ key?: string | undefined; certificate?: string | undefined; certificateAuthorities?: string | string[] | undefined; keyPassphrase?: string | undefined; redirectHttpFromPort?: number | undefined; } & { enabled: boolean; keystore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; truststore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; cipherSuites: string[]; supportedProtocols: string[]; clientAuthentication: \"optional\" | \"none\" | \"required\"; }>; readonly port: number; readonly cors: Readonly<{} & { enabled: boolean; allowCredentials: boolean; allowOrigin: string[] | \"*\"[]; }>; readonly autoListen: boolean; readonly shutdownTimeout: moment.Duration; readonly securityResponseHeaders: Readonly<{} & { referrerPolicy: \"origin\" | \"no-referrer\" | \"no-referrer-when-downgrade\" | \"origin-when-cross-origin\" | \"same-origin\" | \"strict-origin\" | \"strict-origin-when-cross-origin\" | \"unsafe-url\" | null; disableEmbedding: boolean; strictTransportSecurity: string | null; xContentTypeOptions: \"nosniff\" | null; permissionsPolicy: string | null; }>; readonly customResponseHeaders: Record; readonly maxPayload: ", + "{ readonly basePath?: string | undefined; readonly uuid?: string | undefined; readonly publicBaseUrl?: string | undefined; readonly name: string; readonly host: string; readonly compression: Readonly<{ referrerWhitelist?: string[] | undefined; } & { enabled: boolean; brotli: Readonly<{} & { enabled: boolean; quality: number; }>; }>; readonly ssl: Readonly<{ key?: string | undefined; certificate?: string | undefined; certificateAuthorities?: string | string[] | undefined; keyPassphrase?: string | undefined; redirectHttpFromPort?: number | undefined; } & { enabled: boolean; keystore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; truststore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; cipherSuites: string[]; supportedProtocols: string[]; clientAuthentication: \"optional\" | \"none\" | \"required\"; }>; readonly port: number; readonly cors: Readonly<{} & { enabled: boolean; allowCredentials: boolean; allowOrigin: string[] | \"*\"[]; }>; readonly autoListen: boolean; readonly shutdownTimeout: moment.Duration; readonly securityResponseHeaders: Readonly<{} & { referrerPolicy: \"origin\" | \"no-referrer\" | \"no-referrer-when-downgrade\" | \"origin-when-cross-origin\" | \"same-origin\" | \"strict-origin\" | \"strict-origin-when-cross-origin\" | \"unsafe-url\" | null; disableEmbedding: boolean; strictTransportSecurity: string | null; xContentTypeOptions: \"nosniff\" | null; permissionsPolicy: string | null; }>; readonly customResponseHeaders: Record; readonly maxPayload: ", { "pluginId": "@kbn/config-schema", "scope": "server", diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 8df81c8485cb1..733eed156a215 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-12-06 +date: 2022-12-08 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 7c4f4a8d02b42..430e5135d8f43 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-12-06 +date: 2022-12-08 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 62651a2972515..1bdbc08b3e0f1 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-12-06 +date: 2022-12-08 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 c09fc4edbe3b2..198fb0efd058a 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-12-06 +date: 2022-12-08 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 7157f3bcf7c9e..d5b1ba8eff546 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-12-06 +date: 2022-12-08 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 69a312c78dbc5..adb7425b404bd 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-12-06 +date: 2022-12-08 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 cef493bcc2cdf..5623325298117 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-12-06 +date: 2022-12-08 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 0b6f211d95bc5..b9ce28b26de09 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-12-06 +date: 2022-12-08 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 4fc87e2929b50..049881ccafb3f 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-12-06 +date: 2022-12-08 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 fa6fae1003f6d..0414e458c1e9b 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-12-06 +date: 2022-12-08 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 96a39d2da03ef..060e3dc0b89b5 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-12-06 +date: 2022-12-08 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 93533bdce2805..bc95e1fede524 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-12-06 +date: 2022-12-08 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 afdc9a15c78d1..36de2a6fdcf30 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-12-06 +date: 2022-12-08 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 feec70aa08cf7..19e0d73e87b61 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-12-06 +date: 2022-12-08 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 05212d195af71..46ec4e3d55bd6 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-12-06 +date: 2022-12-08 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 99c361512f874..90961a021a9dd 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-12-06 +date: 2022-12-08 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 37c69e4448592..b9e44aa6db296 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-12-06 +date: 2022-12-08 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 f283a5d14c691..ddc1fee1c9a90 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-12-06 +date: 2022-12-08 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 92749a0a9851f..b9fa0553eadd9 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-12-06 +date: 2022-12-08 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 8e6cb70f3758c..567b81e2398b4 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-12-06 +date: 2022-12-08 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 69b47e4da2a5d..fc5c03efbf86e 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-12-06 +date: 2022-12-08 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 6b60ccd65f266..a1f14833111a0 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-12-06 +date: 2022-12-08 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 7b491127d8ba9..24edd4fd7db9f 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-12-06 +date: 2022-12-08 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 2672ccd4642dc..ab09bdbce9061 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-12-06 +date: 2022-12-08 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 b9267b4bf4170..daa80142440ee 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-12-06 +date: 2022-12-08 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 527c5f5c41564..f84109d617877 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-12-06 +date: 2022-12-08 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 5169544aa6953..2b897ab8bd2d7 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-12-06 +date: 2022-12-08 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 044e6f6da7e00..7718b79e3269e 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-12-06 +date: 2022-12-08 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 7ea4dec41fdac..313e7e0d33b1f 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-12-06 +date: 2022-12-08 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 761ba08b2e4eb..8226ae0261625 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-12-06 +date: 2022-12-08 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 0d5d764a85494..2688e7cfff62f 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-12-06 +date: 2022-12-08 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 2a34ed909d3dc..b558157b788ca 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-12-06 +date: 2022-12-08 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 23c0901aa37bf..15c2aef58a153 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-12-06 +date: 2022-12-08 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 4fb5c82446f37..863fcd416372c 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-12-06 +date: 2022-12-08 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 7ebee6af491fe..7cb3f94a4d2ae 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-12-06 +date: 2022-12-08 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 737b181e2eb03..999c9711d93d6 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-12-06 +date: 2022-12-08 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 f1d7c495a6f57..48933e4b49584 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-12-06 +date: 2022-12-08 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 1bdc6c9737e53..1818e510e9808 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-12-06 +date: 2022-12-08 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 f89d1a649fce6..7d049953a1805 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-12-06 +date: 2022-12-08 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 685ca3bb626e3..a8a6093775a77 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-12-06 +date: 2022-12-08 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 ab312dbcaf3b1..3b5fe7586bbd4 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-12-06 +date: 2022-12-08 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 41ea98eb389cf..f7df7fc55628d 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-12-06 +date: 2022-12-08 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 7439bc59f65d7..20de1df675f15 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-12-06 +date: 2022-12-08 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 c51398cd2bc68..d2ecdbd943edd 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index 85093ab0de7c1..05a44e1048b41 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 7e2de2e82d43e..33ed01e540e7d 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-12-06 +date: 2022-12-08 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 e121bac91a981..09f39cef8606f 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-12-06 +date: 2022-12-08 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 e5d9c3b6b1183..f51d3393eb5bf 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-12-06 +date: 2022-12-08 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 e89adf7825fd8..6bc31950f2371 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-12-06 +date: 2022-12-08 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 c55cae140748f..d37c347e49a0e 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-12-06 +date: 2022-12-08 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 266eeca5b7b17..d6e3ef6e3441b 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-12-06 +date: 2022-12-08 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 3b7963337688c..6f571ab6405fa 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-12-06 +date: 2022-12-08 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 0f6ee09219fe1..2baee7f52199d 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-12-06 +date: 2022-12-08 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 6db3cf241e453..024145f6c7025 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-12-06 +date: 2022-12-08 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 b05750c4e35ab..7152e7fdd895a 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -668,11 +668,11 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" + "path": "x-pack/plugins/alerting/server/rules_client/common/inject_references.ts" }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/types.ts" + "path": "x-pack/plugins/alerting/server/rules_client/common/inject_references.ts" }, { "plugin": "alerting", @@ -700,11 +700,11 @@ }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts" + "path": "x-pack/plugins/alerting/server/types.ts" }, { "plugin": "alerting", - "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts" + "path": "x-pack/plugins/alerting/server/types.ts" }, { "plugin": "canvas", diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 59d860f9e6e57..d75e2388a8d2f 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-12-06 +date: 2022-12-08 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 fa63ef0e06e3e..0c9fa19e0a21e 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-12-06 +date: 2022-12-08 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 462cb0c15d7f0..1037858300e8a 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-12-06 +date: 2022-12-08 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 8672c40d385f1..9b660a824af81 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-12-06 +date: 2022-12-08 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 fc2c3b5aecbc1..6011ab3c0c53b 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-12-06 +date: 2022-12-08 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 932721d1cfd43..597ccde8de39d 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-12-06 +date: 2022-12-08 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 4bdf38d3cfe86..9fd151c1e8098 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-12-06 +date: 2022-12-08 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 c391e859f6e22..743effe2ec263 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-12-06 +date: 2022-12-08 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 c2a5e83864c37..a8de657964689 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-12-06 +date: 2022-12-08 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 13645b911006d..384cd5373b06c 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-12-06 +date: 2022-12-08 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 aff6802de0a30..098f113d5e5ee 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-12-06 +date: 2022-12-08 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 7078555ea75bf..4f74e4d55ef48 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-12-06 +date: 2022-12-08 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 2e683f68503be..af10ac080a940 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-12-06 +date: 2022-12-08 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 597a1d3f506d1..3f509e0c08940 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-12-06 +date: 2022-12-08 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 faece7243f02e..d6308322de08b 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-12-06 +date: 2022-12-08 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 0d23068a8e239..f60201fce4cef 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index 407949386ac57..f141f22774db4 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index af319b0b46dbf..e041960a2a5e3 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-12-06 +date: 2022-12-08 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 e13168ab66b71..c87e29eaa59a6 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-12-06 +date: 2022-12-08 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 fe488d2b9160b..490d64b62882a 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-12-06 +date: 2022-12-08 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 6588f3db40c35..4bf2ccfe09b4d 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-12-06 +date: 2022-12-08 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 104a3882e38b0..0badc020a0eb4 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-12-06 +date: 2022-12-08 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 7d2f98a1fbee6..8057e87eb5cd6 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-12-06 +date: 2022-12-08 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 9b66c108cd033..9ceeb0bdb0f9a 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-12-06 +date: 2022-12-08 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 870a165def94d..a576b48cc523c 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-12-06 +date: 2022-12-08 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.devdocs.json b/api_docs/kbn_core_ui_settings_common.devdocs.json index 9a3b58162141f..603bbdb0bb853 100644 --- a/api_docs/kbn_core_ui_settings_common.devdocs.json +++ b/api_docs/kbn_core_ui_settings_common.devdocs.json @@ -347,6 +347,29 @@ "path": "src/plugins/discover/server/ui_settings.ts" } ] + }, + { + "parentPluginId": "@kbn/core-ui-settings-common", + "id": "def-common.UiSettingsParams.scope", + "type": "CompoundType", + "tags": [], + "label": "scope", + "description": [ + "\nScope of the setting. `Global` denotes a setting globally available across namespaces. `Namespace` denotes a setting\nscoped to a namespace. The default value is 'namespace'" + ], + "signature": [ + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsScope", + "text": "UiSettingsScope" + }, + " | undefined" + ], + "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -434,7 +457,32 @@ "section": "def-common.DeprecationSettings", "text": "DeprecationSettings" }, - " | undefined; order?: number | undefined; metric?: { type: string; name: string; } | undefined; }" + " | undefined; order?: number | undefined; metric?: { type: string; name: string; } | undefined; scope?: ", + { + "pluginId": "@kbn/core-ui-settings-common", + "scope": "common", + "docId": "kibKbnCoreUiSettingsCommonPluginApi", + "section": "def-common.UiSettingsScope", + "text": "UiSettingsScope" + }, + " | undefined; }" + ], + "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-ui-settings-common", + "id": "def-common.UiSettingsScope", + "type": "Type", + "tags": [], + "label": "UiSettingsScope", + "description": [ + "\nDenotes the scope of the setting" + ], + "signature": [ + "\"namespace\" | \"global\"" ], "path": "packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts", "deprecated": false, diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index c9f981e689d73..9141ef50719d7 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 23 | 0 | 3 | 0 | +| 25 | 0 | 3 | 0 | ## Common diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 4ae0594ed8497..415def8b8a0a7 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-12-06 +date: 2022-12-08 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.devdocs.json b/api_docs/kbn_core_ui_settings_server_internal.devdocs.json index 81c6e14698d39..001529fd6484e 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.devdocs.json +++ b/api_docs/kbn_core_ui_settings_server_internal.devdocs.json @@ -26,9 +26,9 @@ "text": "UiSettingsClient" }, " extends ", - "BaseUiSettingsClient" + "UiSettingsClientCommon" ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -42,7 +42,7 @@ "signature": [ "any" ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -54,63 +54,68 @@ "label": "options", "description": [], "signature": [ - { - "pluginId": "@kbn/core-ui-settings-server-internal", - "scope": "server", - "docId": "kibKbnCoreUiSettingsServerInternalPluginApi", - "section": "def-server.UiSettingsServiceOptions", - "text": "UiSettingsServiceOptions" - } + "UiSettingsServiceOptions" ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.ts", "deprecated": false, "trackAdoption": false, "isRequired": true } ], "returnComment": [] - }, + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-ui-settings-server-internal", + "id": "def-server.UiSettingsGlobalClient", + "type": "Class", + "tags": [], + "label": "UiSettingsGlobalClient", + "description": [ + "\nGlobal UiSettingsClient" + ], + "signature": [ { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsClient.getUserProvided", - "type": "Function", - "tags": [], - "label": "getUserProvided", - "description": [], - "signature": [ - "() => Promise>" - ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] + "pluginId": "@kbn/core-ui-settings-server-internal", + "scope": "server", + "docId": "kibKbnCoreUiSettingsServerInternalPluginApi", + "section": "def-server.UiSettingsGlobalClient", + "text": "UiSettingsGlobalClient" }, + " extends ", + "UiSettingsClientCommon" + ], + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsClient.setMany", + "id": "def-server.UiSettingsGlobalClient.Unnamed", "type": "Function", "tags": [], - "label": "setMany", + "label": "Constructor", "description": [], "signature": [ - "(changes: Record) => Promise" + "any" ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsClient.setMany.$1", + "id": "def-server.UiSettingsGlobalClient.Unnamed.$1", "type": "Object", "tags": [], - "label": "changes", + "label": "options", "description": [], "signature": [ - "Record" + "UiSettingsServiceOptions" ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -120,44 +125,29 @@ }, { "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsClient.set", + "id": "def-server.UiSettingsGlobalClient.setMany", "type": "Function", "tags": [], - "label": "set", + "label": "setMany", "description": [], "signature": [ - "(key: string, value: any) => Promise" + "(changes: Record) => Promise" ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsClient.set.$1", - "type": "string", - "tags": [], - "label": "key", - "description": [], - "signature": [ - "string" - ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsClient.set.$2", - "type": "Any", + "id": "def-server.UiSettingsGlobalClient.setMany.$1", + "type": "Object", "tags": [], - "label": "value", + "label": "changes", "description": [], "signature": [ - "any" + "Record" ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -167,21 +157,21 @@ }, { "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsClient.remove", + "id": "def-server.UiSettingsGlobalClient.set", "type": "Function", "tags": [], - "label": "remove", + "label": "set", "description": [], "signature": [ - "(key: string) => Promise" + "(key: string, value: any) => Promise" ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsClient.remove.$1", + "id": "def-server.UiSettingsGlobalClient.set.$1", "type": "string", "tags": [], "label": "key", @@ -189,39 +179,22 @@ "signature": [ "string" ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts", "deprecated": false, "trackAdoption": false, "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsClient.removeMany", - "type": "Function", - "tags": [], - "label": "removeMany", - "description": [], - "signature": [ - "(keys: string[]) => Promise" - ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + }, { "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsClient.removeMany.$1", - "type": "Array", + "id": "def-server.UiSettingsGlobalClient.set.$2", + "type": "Any", "tags": [], - "label": "keys", + "label": "value", "description": [], "signature": [ - "string[]" + "any" ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", + "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -268,131 +241,7 @@ "initialIsOpen": false } ], - "interfaces": [ - { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsServiceOptions", - "type": "Interface", - "tags": [], - "label": "UiSettingsServiceOptions", - "description": [], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsServiceOptions.type", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsServiceOptions.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsServiceOptions.buildNum", - "type": "number", - "tags": [], - "label": "buildNum", - "description": [], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsServiceOptions.savedObjectsClient", - "type": "Object", - "tags": [], - "label": "savedObjectsClient", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-saved-objects-api-server", - "scope": "server", - "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", - "section": "def-server.SavedObjectsClientContract", - "text": "SavedObjectsClientContract" - } - ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsServiceOptions.overrides", - "type": "Object", - "tags": [], - "label": "overrides", - "description": [], - "signature": [ - "Record | undefined" - ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsServiceOptions.defaults", - "type": "Object", - "tags": [], - "label": "defaults", - "description": [], - "signature": [ - "Record> | undefined" - ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/core-ui-settings-server-internal", - "id": "def-server.UiSettingsServiceOptions.log", - "type": "Object", - "tags": [], - "label": "log", - "description": [], - "signature": [ - { - "pluginId": "@kbn/logging", - "scope": "server", - "docId": "kibKbnLoggingPluginApi", - "section": "def-server.Logger", - "text": "Logger" - } - ], - "path": "packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - } - ], + "interfaces": [], "enums": [], "misc": [], "objects": [ diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index e917187cbd9d5..bd28003890d14 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 28 | 1 | 28 | 2 | +| 18 | 1 | 17 | 3 | ## Server @@ -34,6 +34,3 @@ Contact Kibana Core for questions regarding this plugin. ### Classes -### Interfaces - - diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index bed23f998989f..082a49aa5ed16 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-12-06 +date: 2022-12-08 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 4641db206c3e5..1a79381268566 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-12-06 +date: 2022-12-08 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 5ee595e321734..b99c41f5a05e2 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-12-06 +date: 2022-12-08 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 1c8d8f92e5b30..ab42690b80ab3 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-12-06 +date: 2022-12-08 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 6f8c214b8e5d8..7f17431a56f72 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-12-06 +date: 2022-12-08 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 572ff6f3a8fb6..faedf9fa701ac 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-12-06 +date: 2022-12-08 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 d3ca2440ecfd1..bc84e1fae44d8 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-12-06 +date: 2022-12-08 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 80d06db6c0381..7272dc59cb4b0 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-12-06 +date: 2022-12-08 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 231419258159c..4a561c43f4aac 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-12-06 +date: 2022-12-08 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 3ecdd330b90f9..3aaced7b707aa 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-12-06 +date: 2022-12-08 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 0599f20d0690d..fda56be800720 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 36faa22dca4d7..f53bdcaae17d0 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-12-06 +date: 2022-12-08 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 cf2f7734574bc..f1a449d9b1d73 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-12-06 +date: 2022-12-08 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 eb8869751d6d6..60921a34e5c08 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-12-06 +date: 2022-12-08 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 83f5465e0e22a..85274e244f6c8 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-12-06 +date: 2022-12-08 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 9559b919787ac..5d1d2de344049 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-12-06 +date: 2022-12-08 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 bf4860207b72f..04421466cda96 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-12-06 +date: 2022-12-08 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 c457675b1ff65..e3bf147690d6f 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-12-06 +date: 2022-12-08 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 a1f60fe69e4a5..c114bba5dab9d 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-12-06 +date: 2022-12-08 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 78d178b0e85db..2bc0ff59420bd 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-12-06 +date: 2022-12-08 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 2a0c01e78f487..7c3ca77e40700 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-12-06 +date: 2022-12-08 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 0df1f66094a09..c7a74b4774b9b 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-12-06 +date: 2022-12-08 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 5418d72188074..df0fd50ec32fa 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-12-06 +date: 2022-12-08 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 eb56f7fb40595..36b0750e354a9 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-12-06 +date: 2022-12-08 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 cf2a8683c5684..c0b5a21988711 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-12-06 +date: 2022-12-08 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 72267a4b32c50..8f3f23c2de39d 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-12-06 +date: 2022-12-08 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 aaa32ce7c2956..b742a9ec4185d 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-12-06 +date: 2022-12-08 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 6b1ff10552ff7..2106acf6e8162 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-12-06 +date: 2022-12-08 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 3dc202feb5468..f34df6015260c 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-12-06 +date: 2022-12-08 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 5787c0a64b061..1067f19777337 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-12-06 +date: 2022-12-08 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 2f48f3535cbf0..6d6cf8c069e2d 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-12-06 +date: 2022-12-08 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 7f601a4e6b157..14d495ad8995f 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-12-06 +date: 2022-12-08 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 dac104a2e6207..2627e2e58c155 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-12-06 +date: 2022-12-08 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 921b776cf6d2f..97cfb60a00997 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-12-06 +date: 2022-12-08 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 5270da2a8f613..9c7b065d93a3e 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-12-06 +date: 2022-12-08 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 172d157c6b878..1f6a30da69f48 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-12-06 +date: 2022-12-08 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 31bf1187bb828..7e6c69b911a85 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-12-06 +date: 2022-12-08 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 6a183876cb3df..e708822aedfea 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-12-06 +date: 2022-12-08 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 6b281e82d195b..7a7022f17e862 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-12-06 +date: 2022-12-08 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 889cab6af3046..3f3d3acb9b655 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-12-06 +date: 2022-12-08 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 03ef626fe2ec5..a0775d477a36a 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-12-06 +date: 2022-12-08 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 493c6fd536809..3bb564314ca3d 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-12-06 +date: 2022-12-08 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 270b052d1f45a..0e1fa4e3eeb7e 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-12-06 +date: 2022-12-08 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 f6be64a927aa4..33f3d9f18b2d3 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-12-06 +date: 2022-12-08 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.mdx b/api_docs/kbn_ml_agg_utils.mdx index d7288b6ddeacb..1553c9b144f43 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 80af6c1ace908..612d7fbbe11fc 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-12-06 +date: 2022-12-08 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 bb878933973a2..3c937a50e66b4 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-12-06 +date: 2022-12-08 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.devdocs.json b/api_docs/kbn_monaco.devdocs.json index 4bcaaaf4efbfd..c7fe8ddfb47bf 100644 --- a/api_docs/kbn_monaco.devdocs.json +++ b/api_docs/kbn_monaco.devdocs.json @@ -35,6 +35,14 @@ "section": "def-common.LangModuleType", "text": "LangModuleType" }, + " | ", + { + "pluginId": "@kbn/monaco", + "scope": "common", + "docId": "kibKbnMonacoPluginApi", + "section": "def-common.CustomLangModuleType", + "text": "CustomLangModuleType" + }, ") => void" ], "path": "packages/kbn-monaco/src/helpers.ts", @@ -44,7 +52,7 @@ { "parentPluginId": "@kbn/monaco", "id": "def-common.registerLanguage.$1", - "type": "Object", + "type": "CompoundType", "tags": [], "label": "language", "description": [], @@ -55,6 +63,14 @@ "docId": "kibKbnMonacoPluginApi", "section": "def-common.LangModuleType", "text": "LangModuleType" + }, + " | ", + { + "pluginId": "@kbn/monaco", + "scope": "common", + "docId": "kibKbnMonacoPluginApi", + "section": "def-common.CustomLangModuleType", + "text": "CustomLangModuleType" } ], "path": "packages/kbn-monaco/src/helpers.ts", @@ -68,6 +84,60 @@ } ], "interfaces": [ + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.BaseWorkerDefinition", + "type": "Interface", + "tags": [], + "label": "BaseWorkerDefinition", + "description": [], + "path": "packages/kbn-monaco/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.BaseWorkerDefinition.getSyntaxErrors", + "type": "Function", + "tags": [], + "label": "getSyntaxErrors", + "description": [], + "signature": [ + "(modelUri: string) => Promise<", + { + "pluginId": "@kbn/monaco", + "scope": "common", + "docId": "kibKbnMonacoPluginApi", + "section": "def-common.EditorError", + "text": "EditorError" + }, + "[] | undefined>" + ], + "path": "packages/kbn-monaco/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.BaseWorkerDefinition.getSyntaxErrors.$1", + "type": "string", + "tags": [], + "label": "modelUri", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-monaco/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/monaco", "id": "def-common.CompleteLangModuleType", @@ -168,6 +238,53 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.CustomLangModuleType", + "type": "Interface", + "tags": [], + "label": "CustomLangModuleType", + "description": [], + "signature": [ + { + "pluginId": "@kbn/monaco", + "scope": "common", + "docId": "kibKbnMonacoPluginApi", + "section": "def-common.CustomLangModuleType", + "text": "CustomLangModuleType" + }, + " extends ", + { + "pluginId": "@kbn/monaco", + "scope": "common", + "docId": "kibKbnMonacoPluginApi", + "section": "def-common.LangModuleType", + "text": "LangModuleType" + } + ], + "path": "packages/kbn-monaco/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.CustomLangModuleType.onLanguage", + "type": "Function", + "tags": [], + "label": "onLanguage", + "description": [], + "signature": [ + "() => void" + ], + "path": "packages/kbn-monaco/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/monaco", "id": "def-common.EditorError", @@ -268,7 +385,7 @@ "description": [], "signature": [ "languages", - ".IMonarchLanguage" + ".IMonarchLanguage | undefined" ], "path": "packages/kbn-monaco/src/types.ts", "deprecated": false, @@ -288,34 +405,6 @@ "path": "packages/kbn-monaco/src/types.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "@kbn/monaco", - "id": "def-common.LangModuleType.getSuggestionProvider", - "type": "Object", - "tags": [], - "label": "getSuggestionProvider", - "description": [], - "signature": [ - "Function | undefined" - ], - "path": "packages/kbn-monaco/src/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/monaco", - "id": "def-common.LangModuleType.getSyntaxErrors", - "type": "Object", - "tags": [], - "label": "getSyntaxErrors", - "description": [], - "signature": [ - "Function | undefined" - ], - "path": "packages/kbn-monaco/src/types.ts", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -573,6 +662,36 @@ ], "enums": [], "misc": [ + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.ESQL_LANG_ID", + "type": "string", + "tags": [], + "label": "ESQL_LANG_ID", + "description": [], + "signature": [ + "\"esql\"" + ], + "path": "packages/kbn-monaco/src/esql/lib/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.ESQL_THEME_ID", + "type": "string", + "tags": [], + "label": "ESQL_THEME_ID", + "description": [], + "signature": [ + "\"esqlTheme\"" + ], + "path": "packages/kbn-monaco/src/esql/lib/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/monaco", "id": "def-common.PainlessCompletionKind", @@ -605,46 +724,6 @@ } ], "objects": [ - { - "parentPluginId": "@kbn/monaco", - "id": "def-common.EsqlLang", - "type": "Object", - "tags": [], - "label": "EsqlLang", - "description": [], - "path": "packages/kbn-monaco/src/esql/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/monaco", - "id": "def-common.EsqlLang.ID", - "type": "string", - "tags": [], - "label": "ID", - "description": [], - "path": "packages/kbn-monaco/src/esql/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/monaco", - "id": "def-common.EsqlLang.lexerRules", - "type": "Object", - "tags": [], - "label": "lexerRules", - "description": [], - "signature": [ - "languages", - ".IMonarchLanguage" - ], - "path": "packages/kbn-monaco/src/esql/index.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/monaco", "id": "def-common.PainlessLang", @@ -817,6 +896,46 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.SQLLang", + "type": "Object", + "tags": [], + "label": "SQLLang", + "description": [], + "path": "packages/kbn-monaco/src/sql/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.SQLLang.ID", + "type": "string", + "tags": [], + "label": "ID", + "description": [], + "path": "packages/kbn-monaco/src/sql/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/monaco", + "id": "def-common.SQLLang.lexerRules", + "type": "Object", + "tags": [], + "label": "lexerRules", + "description": [], + "signature": [ + "languages", + ".IMonarchLanguage" + ], + "path": "packages/kbn-monaco/src/sql/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/monaco", "id": "def-common.XJsonLang", diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index d684a319e2944..6bb3d41bd64ee 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 55 | 0 | 55 | 2 | +| 60 | 0 | 60 | 2 | ## Common diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index fb14195a42224..aee94cf8cbce9 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-12-06 +date: 2022-12-08 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 2e7b88625fd76..a07101650d083 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-12-06 +date: 2022-12-08 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 c545b16c76e6b..dd0fd41b965ef 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-12-06 +date: 2022-12-08 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_peggy.mdx b/api_docs/kbn_peggy.mdx index 4a87f715dd78c..cdf7c1370ead6 100644 --- a/api_docs/kbn_peggy.mdx +++ b/api_docs/kbn_peggy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-peggy title: "@kbn/peggy" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/peggy plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/peggy'] --- import kbnPeggyObj from './kbn_peggy.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 57dcb6021e950..b7d64df60d5c6 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-12-06 +date: 2022-12-08 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 9c796ba3c04ad..3ca5688c21c86 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-12-06 +date: 2022-12-08 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 de87b3df787ec..73e2462094952 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-12-06 +date: 2022-12-08 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 8ee50b467ce60..eaee1b559db38 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-12-06 +date: 2022-12-08 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 8ccf71cb57864..9133c1377eb95 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 6736b2fd24754..f1f9120c79261 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index 403479f0c300a..74b279fa11a15 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -1401,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\" | \"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\"" + "\"tags\" | \"kibana\" | \"@timestamp\" | \"event.action\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"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, diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 58c892f9ea5d1..77d8a832af766 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 2a67b8f0b2a45..43d909fc89ffb 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-12-06 +date: 2022-12-08 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 9ddc1a20ad2b5..a76bb26753df9 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index bf5dccc0e220b..115771988455d 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 3083b3254d881..37b4b57615d14 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-12-06 +date: 2022-12-08 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 f6a76b3a11330..1ca7397a98be0 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index bf51768f895cd..f7c384dc84217 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index b4a088e907489..fd2a78216be34 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-12-06 +date: 2022-12-08 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 5c9e710eab355..8f5b3978bd8ed 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 184aae4efbdec..697b0af8dcea9 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-12-06 +date: 2022-12-08 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.devdocs.json b/api_docs/kbn_securitysolution_list_constants.devdocs.json index 4b06a6e96b7c8..33636a6103033 100644 --- a/api_docs/kbn_securitysolution_list_constants.devdocs.json +++ b/api_docs/kbn_securitysolution_list_constants.devdocs.json @@ -251,6 +251,14 @@ "deprecated": true, "trackAdoption": false, "references": [ + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" @@ -303,14 +311,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/service/api_client.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts" diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index e8f2fd2f8a0f7..3e2a3678210e7 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index a1cf9ab583b8c..43a7ba047c1b0 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-12-06 +date: 2022-12-08 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 110857778b1fb..cdc54f34e92a7 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-12-06 +date: 2022-12-08 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 bef2cb875a16d..a295805f9dcee 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-12-06 +date: 2022-12-08 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 b7a7096ffbca7..90df79701d76f 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-12-06 +date: 2022-12-08 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 6a1e9117265c4..fc8ba0f82395a 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-12-06 +date: 2022-12-08 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 e04a04c425a8f..50c4313aa41af 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-12-06 +date: 2022-12-08 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 db6669b2c2c82..1343f1ebfaa84 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-12-06 +date: 2022-12-08 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 5a952d9c12768..feb39740f2c24 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-12-06 +date: 2022-12-08 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 975b27be5c27b..56f131d672912 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-12-06 +date: 2022-12-08 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 d7d9bcf18c15a..6048a1503ed65 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-12-06 +date: 2022-12-08 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 3aaeb39134631..02529f5eb1562 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-12-06 +date: 2022-12-08 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 92bad13340a97..9fa4bf2141d3a 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-12-06 +date: 2022-12-08 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 b7947a169a085..ce448bac7fb10 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-12-06 +date: 2022-12-08 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 70f22a645a033..afd91c9a887a5 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-12-06 +date: 2022-12-08 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 3e77af0a80a2a..01eca0aafc1fb 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-12-06 +date: 2022-12-08 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_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index e9602d0f013ca..ae66d93bbe208 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index 5f23b2f93f934..fe6d118da48b3 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index efb441df915b6..3e9640d2b211d 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index e857d341cedae..f1e9b4932045a 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index 430f2cc900a82..cbb08b48dd82b 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index e2854a67bfab8..2fa11f88060cc 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-12-06 +date: 2022-12-08 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 644155759d1c7..20b134fb1115b 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-12-06 +date: 2022-12-08 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 ec169705ca905..d7a4d5296a1ca 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-12-06 +date: 2022-12-08 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 51be49cf40b2c..8e57e2067217e 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-12-06 +date: 2022-12-08 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 855b42b2d5a66..d799655908e0d 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-12-06 +date: 2022-12-08 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 49ec03b16234b..80b9a6f2bc428 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-12-06 +date: 2022-12-08 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 5d3658dee5d49..f0b72cfe40b34 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-12-06 +date: 2022-12-08 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 41d50fc164b8e..361ae6d2a4f75 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-12-06 +date: 2022-12-08 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 1cfb3c821f08c..ecc73b2980485 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-12-06 +date: 2022-12-08 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 895228de33167..bfb6cda50579a 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-12-06 +date: 2022-12-08 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 b8a0f2f427199..7b56803a06446 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-12-06 +date: 2022-12-08 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.devdocs.json b/api_docs/kbn_shared_ux_page_no_data_config.devdocs.json index 73e12b09b9d23..56686c1327cb2 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.devdocs.json +++ b/api_docs/kbn_shared_ux_page_no_data_config.devdocs.json @@ -173,9 +173,9 @@ "label": "props", "description": [], "signature": [ - "P & Pick<", - "KibanaPageTemplateProps", - ", \"pageSideBar\" | \"pageSideBarProps\"> & { children?: React.ReactNode; } & { solutionNav: ", + "P & ", + "TemplateProps", + " & { solutionNav: ", { "pluginId": "@kbn/shared-ux-page-solution-nav", "scope": "common", 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 a99824e974139..de4c012609171 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-12-06 +date: 2022-12-08 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 b4a093c4eef94..073bc4a02f9e5 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-12-06 +date: 2022-12-08 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 c1dcdb87f1cf8..747e89a7670ca 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-12-06 +date: 2022-12-08 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.devdocs.json b/api_docs/kbn_shared_ux_page_solution_nav.devdocs.json index 4d792171e0bbc..7580d764c8edc 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.devdocs.json +++ b/api_docs/kbn_shared_ux_page_solution_nav.devdocs.json @@ -78,7 +78,9 @@ "label": "withSolutionNav", "description": [], "signature": [ - "

(WrappedComponent: React.ComponentType

) => { (props: Props

): JSX.Element; displayName: string; }" + "

(WrappedComponent: React.ComponentType

) => { (props: Props

): JSX.Element; displayName: string; }" ], "path": "packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx", "deprecated": false, diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index 5c7191ae761db..ecdd786f08fcc 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 5 | 0 | 3 | 0 | +| 5 | 0 | 3 | 1 | ## Common 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 7a1fdec933db4..7bc589e1f91c5 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-12-06 +date: 2022-12-08 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 f05ddc5cf3d3a..77fc06ee54a15 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index 76be341384b9f..0800d2782eebb 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 8e2b4ab96a3c1..631c31363e45c 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-12-06 +date: 2022-12-08 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 b7123b3a3cd72..856341d3c8724 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-12-06 +date: 2022-12-08 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 55125706f1495..8de2cc806aafa 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-12-06 +date: 2022-12-08 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 ab0da30ffe19e..42ff1726f2622 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-12-06 +date: 2022-12-08 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 095cd3b29e241..117805b315520 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-12-06 +date: 2022-12-08 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 a7540897610d3..4e2fae817bf94 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-12-06 +date: 2022-12-08 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 e2ed020d653fc..87c15a389ffe9 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-12-06 +date: 2022-12-08 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 0493f6cf49de1..116b31639670e 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-12-06 +date: 2022-12-08 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 bbd7d580bc8ed..08a43817b5dd2 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-12-06 +date: 2022-12-08 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 3f50aa6eb4492..7ff24f8533573 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-12-06 +date: 2022-12-08 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 24b0aa2b2ac0b..ccf6dd8f1be6d 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-12-06 +date: 2022-12-08 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 9b444b117757a..412dd4370b641 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-12-06 +date: 2022-12-08 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 4fb251e15c4a1..d4381a626b2b8 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-12-06 +date: 2022-12-08 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 5fd856539bbe7..7661bf10788f5 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-12-06 +date: 2022-12-08 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 7cad7bea538cb..e10f102b5fd90 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-12-06 +date: 2022-12-08 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 dd3c6057d0e52..59d6ac0e7370a 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-12-06 +date: 2022-12-08 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 79d2d7563d148..e31f2f3f0d7d5 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-12-06 +date: 2022-12-08 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 3950e617fc4a8..364a264633a06 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-12-06 +date: 2022-12-08 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 2b91876286436..9806e36fe1876 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-12-06 +date: 2022-12-08 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 db31d2c7eeb7b..69f17829eec96 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-12-06 +date: 2022-12-08 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 1ac8cf70546aa..59175574d7dfa 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-12-06 +date: 2022-12-08 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 58195dbec63f8..11d126b41db53 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-12-06 +date: 2022-12-08 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 ed62483f69f94..575fd7c9c0335 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-12-06 +date: 2022-12-08 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 c495abb76eedb..4d070f49d8f44 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-12-06 +date: 2022-12-08 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 a0a3f2d6cbcb4..152cb6467b704 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-12-06 +date: 2022-12-08 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 18b98ee83d16a..443df579969d1 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 6b5f8816ff972..5f113ddcf0e6f 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-12-06 +date: 2022-12-08 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 4a273553298a6..644f6083ff6bf 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-12-06 +date: 2022-12-08 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 dc3fbbf30d5af..671a5e0244f0b 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index 4762dc2ec8807..30753b6ceae20 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -2330,7 +2330,15 @@ "section": "def-common.DataView", "text": "DataView" }, - ") => ", + ", dateRange?: ", + { + "pluginId": "lens", + "scope": "common", + "docId": "kibLensPluginApi", + "section": "def-common.DateRange", + "text": "DateRange" + }, + " | undefined) => ", { "pluginId": "lens", "scope": "public", @@ -2508,6 +2516,28 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "lens", + "id": "def-public.FormulaPublicApi.insertOrReplaceFormulaColumn.$5", + "type": "Object", + "tags": [], + "label": "dateRange", + "description": [], + "signature": [ + { + "pluginId": "lens", + "scope": "common", + "docId": "kibLensPluginApi", + "section": "def-common.DateRange", + "text": "DateRange" + }, + " | undefined" + ], + "path": "x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [] diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index ac739e788c61e..f616758783f69 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualization | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 693 | 0 | 597 | 50 | +| 694 | 0 | 598 | 50 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 099b953cf7274..142b5d4fcba3b 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-12-06 +date: 2022-12-08 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 a4a5fbaa4924d..a2d42dce0ff96 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-12-06 +date: 2022-12-08 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 3be720e7fca50..5205aca1c51a1 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index e3962d6ab632c..364583ce7a582 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 374a530ff8309..a7cf85af82ffe 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-12-06 +date: 2022-12-08 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 64b959f6bbf39..98fdf907a8418 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-12-06 +date: 2022-12-08 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 c91506cb927e1..0e8dbfa9d3e18 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 3fb8911bcc351..daff92fbd9253 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-12-06 +date: 2022-12-08 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 079c9e8160afc..ac221cc77dd9d 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-12-06 +date: 2022-12-08 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 8d768b4f7450e..2a045f17e5131 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-12-06 +date: 2022-12-08 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 b707bba302189..c0cde511d36ad 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-12-06 +date: 2022-12-08 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 4d680a6dcea18..a05ccf3aed8eb 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-12-06 +date: 2022-12-08 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 5f8c939fcde4e..c6d05d225771c 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-12-06 +date: 2022-12-08 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 d53c00260d05c..e76854bbb52f0 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -830,6 +830,78 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityAlertSearchBar", + "type": "Function", + "tags": [], + "label": "ObservabilityAlertSearchBar", + "description": [], + "signature": [ + "(props: ", + "ObservabilityAlertSearchBarProps", + ") => JSX.Element" + ], + "path": "x-pack/plugins/observability/public/components/shared/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityAlertSearchBar.$1", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "ObservabilityAlertSearchBarProps" + ], + "path": "x-pack/plugins/observability/public/components/shared/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityAlertSearchBarProvider", + "type": "Function", + "tags": [], + "label": "ObservabilityAlertSearchBarProvider", + "description": [], + "signature": [ + "({ children, data: { query: { timefilter: { timefilter: timeFilterService }, }, }, useToasts, triggersActionsUi: { getAlertsSearchBar: AlertsSearchBar }, }: React.PropsWithChildren<", + "Dependencies", + ">) => JSX.Element" + ], + "path": "x-pack/plugins/observability/public/components/shared/alert_search_bar/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityAlertSearchBarProvider.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n children,\n data: {\n query: {\n timefilter: { timefilter: timeFilterService },\n },\n },\n useToasts,\n triggersActionsUi: { getAlertsSearchBar: AlertsSearchBar },\n}", + "description": [], + "signature": [ + "React.PropsWithChildren<", + "Dependencies", + ">" + ], + "path": "x-pack/plugins/observability/public/components/shared/alert_search_bar/services.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-public.Prompt", @@ -3438,6 +3510,54 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsSetup.share", + "type": "CompoundType", + "tags": [], + "label": "share", + "description": [], + "signature": [ + "{ register: (shareMenuProvider: ", + { + "pluginId": "share", + "scope": "public", + "docId": "kibSharePluginApi", + "section": "def-public.ShareMenuProvider", + "text": "ShareMenuProvider" + }, + ") => void; } & { url: ", + { + "pluginId": "share", + "scope": "public", + "docId": "kibSharePluginApi", + "section": "def-public.BrowserUrlService", + "text": "BrowserUrlService" + }, + "; navigate(options: ", + "RedirectOptions", + "<", + { + "pluginId": "@kbn/utility-types", + "scope": "server", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-server.SerializableRecord", + "text": "SerializableRecord" + }, + ">): void; setAnonymousAccessServiceProvider: (provider: () => ", + { + "pluginId": "share", + "scope": "common", + "docId": "kibSharePluginApi", + "section": "def-common.AnonymousAccessServiceContract", + "text": "AnonymousAccessServiceContract" + }, + ") => void; }" + ], + "path": "x-pack/plugins/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "observability", "id": "def-public.ObservabilityPublicPluginsSetup.triggersActionsUi", @@ -3594,6 +3714,46 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsStart.share", + "type": "CompoundType", + "tags": [], + "label": "share", + "description": [], + "signature": [ + "{ toggleShareContextMenu: (options: ", + { + "pluginId": "share", + "scope": "public", + "docId": "kibSharePluginApi", + "section": "def-public.ShowShareMenuOptions", + "text": "ShowShareMenuOptions" + }, + ") => void; } & { url: ", + { + "pluginId": "share", + "scope": "public", + "docId": "kibSharePluginApi", + "section": "def-public.BrowserUrlService", + "text": "BrowserUrlService" + }, + "; navigate(options: ", + "RedirectOptions", + "<", + { + "pluginId": "@kbn/utility-types", + "scope": "server", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-server.SerializableRecord", + "text": "SerializableRecord" + }, + ">): void; }" + ], + "path": "x-pack/plugins/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "observability", "id": "def-public.ObservabilityPublicPluginsStart.triggersActionsUi", @@ -7550,7 +7710,7 @@ "label": "kqlQuery", "description": [], "signature": [ - "(kql: string) => ", + "(kql: string | undefined) => ", "QueryDslQueryContainer", "[]" ], @@ -7566,12 +7726,12 @@ "label": "kql", "description": [], "signature": [ - "string" + "string | undefined" ], "path": "x-pack/plugins/observability/server/utils/queries.ts", "deprecated": false, "trackAdoption": false, - "isRequired": true + "isRequired": false } ], "returnComment": [], @@ -11764,6 +11924,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observability", + "id": "def-common.ruleDetailsLocatorID", + "type": "string", + "tags": [], + "label": "ruleDetailsLocatorID", + "description": [], + "signature": [ + "\"RULE_DETAILS_LOCATOR\"" + ], + "path": "x-pack/plugins/observability/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observability", "id": "def-common.SYNTHETICS_BLOCKED_TIMINGS", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 25f52f331133e..df317960f6629 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Observability UI](https://github.com/orgs/elastic/teams/observability-u | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 571 | 40 | 567 | 31 | +| 578 | 40 | 574 | 33 | ## Client diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 0666c62f78f5d..7ba3a4f2f231f 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-12-06 +date: 2022-12-08 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 7dcaa3a357e84..da13f780e79f1 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -21,7 +21,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 33860 | 520 | 23578 | 1149 | +| 33682 | 520 | 23458 | 1150 | ## Plugin Directory @@ -30,7 +30,7 @@ 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. | 12 | 0 | 1 | 2 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 417 | 0 | 408 | 28 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 417 | 0 | 408 | 34 | | | [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 | @@ -45,23 +45,23 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | cloudLinks | [Kibana Core](https://github.com/orgs/elastic/teams/@kibana-core) | Adds the links to the Elastic Cloud console | 0 | 0 | 0 | 0 | | | [Cloud Security Posture](https://github.com/orgs/elastic/teams/cloud-posture-security) | The cloud security posture plugin | 17 | 0 | 2 | 2 | | | [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 | 267 | 0 | 258 | 10 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2796 | 17 | 1007 | 0 | +| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 268 | 0 | 259 | 10 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2798 | 17 | 1007 | 0 | | crossClusterReplication | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | customBranding | [global-experience](https://github.com/orgs/elastic/teams/kibana-global-experience) | Enables customization of Kibana | 0 | 0 | 0 | 0 | | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 107 | 0 | 88 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 121 | 0 | 114 | 3 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 52 | 0 | 51 | 0 | -| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3265 | 119 | 2553 | 27 | +| | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3279 | 119 | 2561 | 27 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 16 | 0 | 7 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Reusable data view field editor across Kibana | 60 | 0 | 30 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data view management app | 2 | 0 | 2 | 0 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1021 | 0 | 227 | 2 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1022 | 0 | 228 | 2 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 28 | 3 | 24 | 1 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 10 | 0 | 8 | 2 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 100 | 0 | 82 | 4 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 510 | 6 | 410 | 4 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 513 | 6 | 413 | 4 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 44 | 0 | | | [Enterprise Search](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 9 | 0 | 9 | 0 | @@ -107,7 +107,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | kibanaUsageCollection | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 0 | 0 | 0 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 624 | 3 | 424 | 8 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 693 | 0 | 597 | 50 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 694 | 0 | 598 | 50 | | | [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 | @@ -122,11 +122,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 34 | 0 | 34 | 2 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 2 | 0 | 2 | 1 | -| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 571 | 40 | 567 | 31 | +| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 578 | 40 | 574 | 33 | | | [Security asset management](https://github.com/orgs/elastic/teams/security-asset-management) | - | 21 | 0 | 21 | 5 | | 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). | 227 | 7 | 171 | 12 | -| | [profiling](https://github.com/orgs/elastic/teams/profiling-ui) | - | 14 | 2 | 14 | 0 | +| | [profiling](https://github.com/orgs/elastic/teams/profiling-ui) | - | 15 | 2 | 15 | 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 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 21 | 0 | 21 | 0 | @@ -142,7 +142,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [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 | 1 | -| | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 112 | 0 | 75 | 27 | +| | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 112 | 0 | 75 | 28 | | | [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 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 22 | 1 | 22 | 1 | @@ -156,7 +156,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 1 | 0 | 1 | 0 | | | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 11 | 0 | 10 | 0 | | | [Protections Experience Team](https://github.com/orgs/elastic/teams/protections-experience) | Elastic threat intelligence helps you see if you are open to or have been subject to current or historical known threats | 34 | 0 | 14 | 3 | -| | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 462 | 1 | 350 | 33 | +| | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 257 | 1 | 214 | 21 | | | [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) | - | 532 | 11 | 503 | 51 | @@ -175,13 +175,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the heatmap implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy heatmap charts library advanced setting. | 3 | 0 | 3 | 2 | | visTypeMarkdown | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds a markdown visualization type | 0 | 0 | 0 | 0 | | visTypeMetric | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Registers the Metric aggregation-based visualization. | 0 | 0 | 0 | 0 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the pie chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting. | 12 | 0 | 12 | 1 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the pie chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting. | 11 | 0 | 11 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Registers the datatable aggregation-based visualization. | 12 | 0 | 12 | 0 | | visTypeTagcloud | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Registers the tagcloud visualization. It is based on elastic-charts wordcloud. | 0 | 0 | 0 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Registers the timelion visualization. Also contains the backend for both timelion app and timelion visualization. | 2 | 0 | 2 | 2 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Registers the TSVB visualization. TSVB has its one editor, works with index patterns and index strings and contains 6 types of charts: timeseries, topN, table. markdown, metric and gauge. | 10 | 1 | 10 | 3 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Registers the vega visualization. Is the elastic version of vega and vega-lite libraries. | 2 | 0 | 2 | 0 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 26 | 0 | 25 | 1 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the vislib visualizations. These are the classical area/line/bar, gauge/goal and heatmap charts. We want to replace them with elastic-charts. | 26 | 0 | 25 | 1 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the new xy-axis chart using the elastic-charts library, which will eventually replace the vislib xy-axis charts including bar, area, and line. | 53 | 0 | 50 | 5 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the shared architecture among all the legacy visualizations, e.g. the visualization type registry or the visualization embeddable. | 797 | 12 | 767 | 18 | | watcher | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | @@ -202,7 +202,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | 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] | Elastic APM trace data generator | 77 | 0 | 77 | 15 | | | [Owner missing] | - | 11 | 0 | 11 | 0 | | | [Owner missing] | - | 10 | 0 | 10 | 0 | | | [Owner missing] | - | 4 | 0 | 3 | 0 | @@ -359,9 +359,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 25 | 1 | 13 | 0 | | | Kibana Core | - | 25 | 1 | 25 | 0 | | | Kibana Core | - | 4 | 0 | 4 | 0 | -| | Kibana Core | - | 23 | 0 | 3 | 0 | +| | Kibana Core | - | 25 | 0 | 3 | 0 | | | Kibana Core | - | 27 | 1 | 13 | 0 | -| | Kibana Core | - | 28 | 1 | 28 | 2 | +| | Kibana Core | - | 18 | 1 | 17 | 3 | | | Kibana Core | - | 6 | 0 | 6 | 0 | | | Kibana Core | - | 153 | 0 | 142 | 0 | | | Kibana Core | - | 8 | 0 | 8 | 2 | @@ -409,7 +409,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | 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 | +| | [Owner missing] | - | 60 | 0 | 60 | 2 | | | [Owner missing] | - | 47 | 0 | 46 | 10 | | | [Owner missing] | - | 51 | 5 | 34 | 0 | | | [Owner missing] | io ts utilities and types to be shared with plugins from the osquery project | 62 | 0 | 62 | 0 | @@ -465,7 +465,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 11 | 0 | 9 | 0 | | | [Owner missing] | - | 24 | 0 | 24 | 0 | | | [Owner missing] | - | 27 | 0 | 26 | 0 | -| | [Owner missing] | - | 5 | 0 | 3 | 0 | +| | [Owner missing] | - | 5 | 0 | 3 | 1 | | | [Owner missing] | - | 25 | 0 | 10 | 0 | | | [Owner missing] | - | 17 | 0 | 16 | 0 | | | [Owner missing] | - | 2 | 0 | 1 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 85f6485348562..853be996619c1 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.devdocs.json b/api_docs/profiling.devdocs.json index 9652c44cfb99d..035a78aebe58c 100644 --- a/api_docs/profiling.devdocs.json +++ b/api_docs/profiling.devdocs.json @@ -13,7 +13,23 @@ "functions": [], "interfaces": [], "enums": [], - "misc": [], + "misc": [ + { + "parentPluginId": "profiling", + "id": "def-server.ProfilingConfig", + "type": "Type", + "tags": [], + "label": "ProfilingConfig", + "description": [], + "signature": [ + "{ readonly elasticsearch?: Readonly<{} & { username: string; password: string; hosts: string; }> | undefined; readonly enabled: boolean; }" + ], + "path": "x-pack/plugins/profiling/server/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "objects": [], "setup": { "parentPluginId": "profiling", diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index af66d2ac52e8d..3fa6776a1c843 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; @@ -21,7 +21,7 @@ Contact [profiling](https://github.com/orgs/elastic/teams/profiling-ui) for ques | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 14 | 2 | 14 | 0 | +| 15 | 2 | 15 | 0 | ## Server @@ -31,6 +31,9 @@ Contact [profiling](https://github.com/orgs/elastic/teams/profiling-ui) for ques ### Start +### Consts, variables and types + + ## Common ### Functions diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index b09b8bec09e5e..b7e56abc94d60 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-12-06 +date: 2022-12-08 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 d121c901fd081..f57b2cc147469 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-12-06 +date: 2022-12-08 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 810a2537f3cf3..5d562d6185385 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index a4c65e081973e..7cbedc3c97f09 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-12-06 +date: 2022-12-08 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 7d67a6ae310b9..438d81ea8f12b 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-12-06 +date: 2022-12-08 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 fd36a83407f44..a5bdcf46e6113 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-12-06 +date: 2022-12-08 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 7ac14f75e2ff4..957e82a7f0226 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.devdocs.json b/api_docs/saved_objects_management.devdocs.json index eec243d87398b..c51e3b00daf11 100644 --- a/api_docs/saved_objects_management.devdocs.json +++ b/api_docs/saved_objects_management.devdocs.json @@ -294,7 +294,7 @@ "label": "euiColumn", "description": [], "signature": [ - "{ children?: React.ReactNode; name: React.ReactNode; description?: string | undefined; onError?: React.ReactEventHandler | undefined; render?: ((value: any, record: ", + "{ children?: React.ReactNode; name: React.ReactNode; description?: string | undefined; scope?: string | undefined; onError?: React.ReactEventHandler | undefined; render?: ((value: any, record: ", { "pluginId": "savedObjectsManagement", "scope": "public", @@ -302,7 +302,7 @@ "section": "def-public.SavedObjectsManagementRecord", "text": "SavedObjectsManagementRecord" }, - ") => React.ReactNode) | undefined; hidden?: boolean | undefined; color?: string | undefined; id?: string | undefined; className?: string | undefined; title?: string | undefined; onChange?: React.FormEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; security?: string | undefined; field: (string & {}) | keyof ", + ") => React.ReactNode) | undefined; hidden?: boolean | undefined; color?: string | undefined; id?: string | undefined; className?: string | undefined; title?: string | undefined; onChange?: React.FormEventHandler | undefined; onKeyDown?: React.KeyboardEventHandler | undefined; onClick?: React.MouseEventHandler | undefined; width?: string | undefined; security?: string | undefined; field: (string & {}) | keyof ", { "pluginId": "savedObjectsManagement", "scope": "public", @@ -314,7 +314,7 @@ "Interpolation", "<", "Theme", - ">; height?: string | number | undefined; width?: string | undefined; readOnly?: boolean | undefined; align?: ", + ">; height?: string | number | undefined; readOnly?: boolean | undefined; align?: ", "HorizontalAlignment", " | undefined; abbr?: string | undefined; footer?: string | React.ReactElement> | ((props: ", "EuiTableFooterProps", @@ -326,7 +326,7 @@ "section": "def-public.SavedObjectsManagementRecord", "text": "SavedObjectsManagementRecord" }, - ">) => React.ReactNode) | undefined; colSpan?: number | undefined; rowSpan?: number | undefined; scope?: string | undefined; valign?: \"top\" | \"bottom\" | \"middle\" | \"baseline\" | undefined; dataType?: ", + ">) => React.ReactNode) | undefined; colSpan?: number | undefined; rowSpan?: number | undefined; valign?: \"top\" | \"bottom\" | \"middle\" | \"baseline\" | undefined; dataType?: ", "EuiTableDataType", " | undefined; isExpander?: boolean | undefined; textOnly?: boolean | undefined; truncateText?: boolean | undefined; mobileOptions?: (Omit<", "EuiTableRowCellMobileOptionsShape", diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 6f2a973f3465c..a616890c63fd3 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-12-06 +date: 2022-12-08 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 28366f90e3a50..567a6ac2badf4 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-12-06 +date: 2022-12-08 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 b0e43505cb269..97b22df6bdd41 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-12-06 +date: 2022-12-08 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 61864a66a5a26..7bd7a5ac90e17 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-12-06 +date: 2022-12-08 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 fda614bc83a44..9e69a31dcbdb6 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-12-06 +date: 2022-12-08 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 f9cc541b394da..acc1db440ea91 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 3d4fc14cd9a44..cf7e42ea51a61 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index 1b7004b155d1d..8138c82cd6b77 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -859,13 +859,7 @@ "The columns displayed in the data table" ], "signature": [ - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderOptions", - "text": "ColumnHeaderOptions" - }, + "ColumnHeaderOptions", "[]" ], "path": "x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts", @@ -880,13 +874,7 @@ "label": "defaultColumns", "description": [], "signature": [ - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderOptions", - "text": "ColumnHeaderOptions" - }, + "ColumnHeaderOptions", "[]" ], "path": "x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts", @@ -1048,17 +1036,17 @@ ], "signature": [ "{ query?: ", - "TimelineExpandedDetailType", + "ExpandedDetailType", " | undefined; graph?: ", - "TimelineExpandedDetailType", + "ExpandedDetailType", " | undefined; notes?: ", - "TimelineExpandedDetailType", + "ExpandedDetailType", " | undefined; pinned?: ", - "TimelineExpandedDetailType", + "ExpandedDetailType", " | undefined; eql?: ", - "TimelineExpandedDetailType", + "ExpandedDetailType", " | undefined; session?: ", - "TimelineExpandedDetailType", + "ExpandedDetailType", " | undefined; }" ], "path": "x-pack/plugins/security_solution/public/timelines/store/timeline/model.ts", @@ -2008,7 +1996,7 @@ "label": "ConfigType", "description": [], "signature": [ - "Readonly<{} & { signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; alertMergeStrategy: \"allFields\" | \"missingFields\" | \"noFields\"; alertIgnoreFields: string[]; enableExperimental: string[]; packagerTaskInterval: string; prebuiltRulesFromFileSystem: boolean; prebuiltRulesFromSavedObjects: boolean; }> & { experimentalFeatures: Readonly<{ tGridEnabled: boolean; tGridEventRenderedViewEnabled: boolean; excludePoliciesInFilterEnabled: boolean; kubernetesEnabled: boolean; disableIsolationUIPendingStatuses: boolean; pendingActionResponsesWithAck: boolean; policyListEnabled: boolean; policyResponseInFleetEnabled: boolean; chartEmbeddablesEnabled: boolean; previewTelemetryUrlEnabled: boolean; responseActionsConsoleEnabled: boolean; insightsRelatedAlertsByProcessAncestry: boolean; extendedRuleExecutionLoggingEnabled: boolean; socTrendsEnabled: boolean; responseActionsEnabled: boolean; endpointRbacEnabled: boolean; endpointRbacV1Enabled: boolean; alertDetailsPageEnabled: boolean; responseActionGetFileEnabled: boolean; riskyHostsEnabled: boolean; riskyUsersEnabled: boolean; }>; }" + "Readonly<{} & { signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; alertMergeStrategy: \"allFields\" | \"missingFields\" | \"noFields\"; alertIgnoreFields: string[]; enableExperimental: string[]; packagerTaskInterval: string; }> & { experimentalFeatures: Readonly<{ tGridEnabled: boolean; tGridEventRenderedViewEnabled: boolean; excludePoliciesInFilterEnabled: boolean; kubernetesEnabled: boolean; disableIsolationUIPendingStatuses: boolean; pendingActionResponsesWithAck: boolean; policyListEnabled: boolean; policyResponseInFleetEnabled: boolean; chartEmbeddablesEnabled: boolean; previewTelemetryUrlEnabled: boolean; responseActionsConsoleEnabled: boolean; insightsRelatedAlertsByProcessAncestry: boolean; extendedRuleExecutionLoggingEnabled: boolean; socTrendsEnabled: boolean; responseActionsEnabled: boolean; endpointRbacEnabled: boolean; endpointRbacV1Enabled: boolean; alertDetailsPageEnabled: boolean; responseActionGetFileEnabled: boolean; riskyHostsEnabled: boolean; riskyUsersEnabled: boolean; }>; }" ], "path": "x-pack/plugins/security_solution/server/config.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index b01233eb7b862..1206afb7eece1 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Security solution](https://github.com/orgs/elastic/teams/security-solut | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 112 | 0 | 75 | 27 | +| 112 | 0 | 75 | 28 | ## Client diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index a6ec7b973cd73..f0e3c333d9458 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-12-06 +date: 2022-12-08 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 469b39680ac01..e0aa5e75714a2 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-12-06 +date: 2022-12-08 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 4f774c08218d7..c9f07d8476a07 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-12-06 +date: 2022-12-08 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 1697ab8495843..d9e72a901faed 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-12-06 +date: 2022-12-08 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 77172023a15c2..1b28caf700042 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-12-06 +date: 2022-12-08 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 3a492d7473f39..81f3766ee0d97 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.devdocs.json b/api_docs/task_manager.devdocs.json index d9497789ba816..451919e575a12 100644 --- a/api_docs/task_manager.devdocs.json +++ b/api_docs/task_manager.devdocs.json @@ -1232,7 +1232,7 @@ "\nA task instance that has an id and is ready for storage." ], "signature": [ - "{ params: Record; enabled?: boolean | undefined; state: Record; scope?: string[] | undefined; taskType: string; }" + "{ scope?: string[] | undefined; params: Record; enabled?: boolean | undefined; state: Record; taskType: string; }" ], "path": "x-pack/plugins/task_manager/server/task.ts", "deprecated": false, diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index b1c3e3324973d..b78aba637baeb 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-12-06 +date: 2022-12-08 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 0a25aa3877b44..28f2601065878 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-12-06 +date: 2022-12-08 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 9898ba4ec976f..f3fb764316241 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-12-06 +date: 2022-12-08 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 cacc0a540af38..6a7be74ea719a 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-12-06 +date: 2022-12-08 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 31fb7d6dc7a9e..8305c1cf83d42 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-12-06 +date: 2022-12-08 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 0e3075a724a1e..731ed603c2ade 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-12-06 +date: 2022-12-08 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 b6d49bf07cf6d..affca9f590a94 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -5,29 +5,31 @@ "functions": [ { "parentPluginId": "timelines", - "id": "def-public.addFieldToTimelineColumns", + "id": "def-public.arrayIndexToAriaIndex", "type": "Function", "tags": [], - "label": "addFieldToTimelineColumns", - "description": [], + "label": "arrayIndexToAriaIndex", + "description": [ + "Converts an array index, which starts at zero, to an aria index, which starts at one" + ], "signature": [ - "({ browserFields, dispatch, result, timelineId, defaultsHeader, }: AddFieldToTimelineColumnsParams) => void" + "(arrayIndex: number) => number" ], - "path": "x-pack/plugins/timelines/public/components/drag_and_drop/helpers.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "timelines", - "id": "def-public.addFieldToTimelineColumns.$1", - "type": "Object", + "id": "def-public.arrayIndexToAriaIndex.$1", + "type": "number", "tags": [], - "label": "{\n browserFields,\n dispatch,\n result,\n timelineId,\n defaultsHeader,\n}", + "label": "arrayIndex", "description": [], "signature": [ - "AddFieldToTimelineColumnsParams" + "number" ], - "path": "x-pack/plugins/timelines/public/components/drag_and_drop/helpers.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -38,63 +40,50 @@ }, { "parentPluginId": "timelines", - "id": "def-public.applyDeltaToColumnWidth", + "id": "def-public.elementOrChildrenHasFocus", "type": "Function", "tags": [], - "label": "applyDeltaToColumnWidth", - "description": [], + "label": "elementOrChildrenHasFocus", + "description": [ + "Returns `true` when the element, or one of it's children has focus" + ], "signature": [ - "ActionCreator", - "<{ id: string; columnId: string; delta: number; }>" + "(element: HTMLElement | null | undefined) => boolean" ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "timelines", - "id": "def-public.applyDeltaToColumnWidth.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.applyDeltaToColumnWidth.$2", + "id": "def-public.elementOrChildrenHasFocus.$1", "type": "CompoundType", "tags": [], - "label": "meta", + "label": "element", "description": [], "signature": [ - "Meta", - " | undefined" + "HTMLElement | null | undefined" ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": false } ], + "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "timelines", - "id": "def-public.arrayIndexToAriaIndex", + "id": "def-public.focusColumn", "type": "Function", "tags": [], - "label": "arrayIndexToAriaIndex", + "label": "focusColumn", "description": [ - "Converts an array index, which starts at zero, to an aria index, which starts at one" + "\nSIDE EFFECT: mutates the DOM by focusing the specified column\nreturns the `aria-colindex` of the newly-focused column" ], "signature": [ - "(arrayIndex: number) => number" + "({ colindexAttribute, containerElement, ariaColindex, ariaRowindex, rowindexAttribute, }: { colindexAttribute: string; containerElement: Element | null; ariaColindex: number; ariaRowindex: number; rowindexAttribute: string; }) => FocusColumnResult" ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, @@ -102,18 +91,74 @@ "children": [ { "parentPluginId": "timelines", - "id": "def-public.arrayIndexToAriaIndex.$1", - "type": "number", + "id": "def-public.focusColumn.$1", + "type": "Object", "tags": [], - "label": "arrayIndex", + "label": "{\n colindexAttribute,\n containerElement,\n ariaColindex,\n ariaRowindex,\n rowindexAttribute,\n}", "description": [], - "signature": [ - "number" - ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "isRequired": true + "children": [ + { + "parentPluginId": "timelines", + "id": "def-public.focusColumn.$1.colindexAttribute", + "type": "string", + "tags": [], + "label": "colindexAttribute", + "description": [], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.focusColumn.$1.containerElement", + "type": "CompoundType", + "tags": [], + "label": "containerElement", + "description": [], + "signature": [ + "Element | null" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.focusColumn.$1.ariaColindex", + "type": "number", + "tags": [], + "label": "ariaColindex", + "description": [], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.focusColumn.$1.ariaRowindex", + "type": "number", + "tags": [], + "label": "ariaRowindex", + "description": [], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.focusColumn.$1.rowindexAttribute", + "type": "string", + "tags": [], + "label": "rowindexAttribute", + "description": [], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], "returnComment": [], @@ -121,236 +166,178 @@ }, { "parentPluginId": "timelines", - "id": "def-public.clearEventsDeleted", + "id": "def-public.getFocusedAriaColindexCell", "type": "Function", "tags": [], - "label": "clearEventsDeleted", - "description": [], + "label": "getFocusedAriaColindexCell", + "description": [ + "\nReturns the focused cell for tables that use `aria-colindex`" + ], "signature": [ - "ActionCreator", - "<{ id: string; }>" + "({ containerElement, tableClassName, }: { containerElement: HTMLElement | null; tableClassName: string; }) => HTMLDivElement | null" ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "timelines", - "id": "def-public.clearEventsDeleted.$1", - "type": "Uncategorized", + "id": "def-public.getFocusedAriaColindexCell.$1", + "type": "Object", "tags": [], - "label": "payload", + "label": "{\n containerElement,\n tableClassName,\n}", "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, - "trackAdoption": false - }, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-public.getFocusedAriaColindexCell.$1.containerElement", + "type": "CompoundType", + "tags": [], + "label": "containerElement", + "description": [], + "signature": [ + "HTMLElement | null" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.getFocusedAriaColindexCell.$1.tableClassName", + "type": "string", + "tags": [], + "label": "tableClassName", + "description": [], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.getFocusedDataColindexCell", + "type": "Function", + "tags": [], + "label": "getFocusedDataColindexCell", + "description": [ + "\nReturns the focused cell for tables that use `data-colindex`" + ], + "signature": [ + "({ containerElement, tableClassName, }: { containerElement: HTMLElement | null; tableClassName: string; }) => HTMLDivElement | null" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "timelines", - "id": "def-public.clearEventsDeleted.$2", - "type": "CompoundType", + "id": "def-public.getFocusedDataColindexCell.$1", + "type": "Object", "tags": [], - "label": "meta", + "label": "{\n containerElement,\n tableClassName,\n}", "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-public.getFocusedDataColindexCell.$1.containerElement", + "type": "CompoundType", + "tags": [], + "label": "containerElement", + "description": [], + "signature": [ + "HTMLElement | null" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.getFocusedDataColindexCell.$1.tableClassName", + "type": "string", + "tags": [], + "label": "tableClassName", + "description": [], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], + "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "timelines", - "id": "def-public.clearEventsLoading", + "id": "def-public.getNotesContainerClassName", "type": "Function", "tags": [], - "label": "clearEventsLoading", + "label": "getNotesContainerClassName", "description": [], "signature": [ - "ActionCreator", - "<{ id: string; }>" + "(ariaRowindex: number) => string" ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "timelines", - "id": "def-public.clearEventsLoading.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.clearEventsLoading.$2", - "type": "CompoundType", + "id": "def-public.getNotesContainerClassName.$1", + "type": "number", "tags": [], - "label": "meta", + "label": "ariaRowindex", "description": [], "signature": [ - "Meta", - " | undefined" + "number" ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true } ], + "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "timelines", - "id": "def-public.clearSelected", + "id": "def-public.getRowRendererClassName", "type": "Function", "tags": [], - "label": "clearSelected", + "label": "getRowRendererClassName", "description": [], "signature": [ - "ActionCreator", - "<{ id: string; }>" + "(ariaRowindex: number) => string" ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "timelines", - "id": "def-public.clearSelected.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.clearSelected.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.combineQueries", - "type": "Function", - "tags": [], - "label": "combineQueries", - "description": [], - "signature": [ - "({ config, dataProviders, indexPattern, browserFields, filters, kqlQuery, kqlMode, }: CombineQueries) => { filterQuery: string | undefined; kqlError: Error | undefined; } | null" - ], - "path": "x-pack/plugins/timelines/public/components/t_grid/helpers.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.combineQueries.$1", - "type": "Object", - "tags": [], - "label": "{\n config,\n dataProviders,\n indexPattern,\n browserFields,\n filters = [],\n kqlQuery,\n kqlMode,\n}", - "description": [], - "signature": [ - "CombineQueries" - ], - "path": "x-pack/plugins/timelines/public/components/t_grid/helpers.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.convertKueryToDslFilter", - "type": "Function", - "tags": [], - "label": "convertKueryToDslFilter", - "description": [], - "signature": [ - "(kueryExpression: string, indexPattern: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.DataViewBase", - "text": "DataViewBase" - }, - ") => ", - "QueryDslQueryContainer" - ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.convertKueryToDslFilter.$1", - "type": "string", - "tags": [], - "label": "kueryExpression", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "timelines", - "id": "def-public.convertKueryToDslFilter.$2", - "type": "Object", + "id": "def-public.getRowRendererClassName.$1", + "type": "number", "tags": [], - "label": "indexPattern", + "label": "ariaRowindex", "description": [], "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.DataViewBase", - "text": "DataViewBase" - } + "number" ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -361,221 +348,130 @@ }, { "parentPluginId": "timelines", - "id": "def-public.convertKueryToElasticSearchQuery", + "id": "def-public.getTableSkipFocus", "type": "Function", "tags": [], - "label": "convertKueryToElasticSearchQuery", - "description": [], - "signature": [ - "(kueryExpression: string, indexPattern?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.DataViewBase", - "text": "DataViewBase" - }, - " | undefined) => string" - ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.convertKueryToElasticSearchQuery.$1", - "type": "string", - "tags": [], - "label": "kueryExpression", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "timelines", - "id": "def-public.convertKueryToElasticSearchQuery.$2", - "type": "Object", - "tags": [], - "label": "indexPattern", - "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.DataViewBase", - "text": "DataViewBase" - }, - " | undefined" - ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } + "label": "getTableSkipFocus", + "description": [ + "\nThis function, which works with tables that use the `aria-colindex` or\n`data-colindex` attributes, examines the focus state of the table, and\nreturns a `SkipFocus` enumeration.\n\nThe `SkipFocus` return value indicates whether the caller should skip focus\nto \"before\" the table, \"after\" the table, or take no action, and let the\nbrowser's \"natural\" focus management manage focus." ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.convertToBuildEsQuery", - "type": "Function", - "tags": [], - "label": "convertToBuildEsQuery", - "description": [], "signature": [ - "({ config, indexPattern, queries, filters, }: { config: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.EsQueryConfig", - "text": "EsQueryConfig" - }, - "; indexPattern: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.DataViewBase", - "text": "DataViewBase" - }, - " | undefined; queries: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - "[]; filters: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]; }) => [string, undefined] | [undefined, Error]" + "({ containerElement, getFocusedCell, shiftKey, tableHasFocus, tableClassName, }: { containerElement: HTMLElement | null; getFocusedCell: ", + "GetFocusedCell", + "; shiftKey: boolean; tableHasFocus: (containerElement: HTMLElement | null) => boolean; tableClassName: string; }) => ", + "SkipFocus" ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "timelines", - "id": "def-public.convertToBuildEsQuery.$1", + "id": "def-public.getTableSkipFocus.$1", "type": "Object", "tags": [], - "label": "{\n config,\n indexPattern,\n queries,\n filters,\n}", + "label": "{\n containerElement,\n getFocusedCell,\n shiftKey,\n tableHasFocus,\n tableClassName,\n}", "description": [], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "timelines", - "id": "def-public.convertToBuildEsQuery.$1.config", + "id": "def-public.getTableSkipFocus.$1.containerElement", "type": "CompoundType", "tags": [], - "label": "config", + "label": "containerElement", "description": [], "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.KueryQueryOptions", - "text": "KueryQueryOptions" - }, - " & ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.EsQueryFiltersConfig", - "text": "EsQueryFiltersConfig" - }, - " & { allowLeadingWildcards?: boolean | undefined; queryStringOptions?: ", - { - "pluginId": "@kbn/utility-types", - "scope": "server", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-server.SerializableRecord", - "text": "SerializableRecord" - }, - " | undefined; }" + "HTMLElement | null" ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "timelines", - "id": "def-public.convertToBuildEsQuery.$1.indexPattern", - "type": "Object", + "id": "def-public.getTableSkipFocus.$1.getFocusedCell", + "type": "Function", "tags": [], - "label": "indexPattern", + "label": "getFocusedCell", "description": [], "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.DataViewBase", - "text": "DataViewBase" - }, - " | undefined" + "({ containerElement, tableClassName, }: { containerElement: HTMLElement | null; tableClassName: string; }) => HTMLDivElement | null" ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "timelines", + "id": "def-public.getTableSkipFocus.$1.getFocusedCell.$1", + "type": "Object", + "tags": [], + "label": "__0", + "description": [], + "signature": [ + "{ containerElement: HTMLElement | null; tableClassName: string; }" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + } + ] }, { "parentPluginId": "timelines", - "id": "def-public.convertToBuildEsQuery.$1.queries", - "type": "Array", + "id": "def-public.getTableSkipFocus.$1.shiftKey", + "type": "boolean", "tags": [], - "label": "queries", + "label": "shiftKey", "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - "[]" - ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "timelines", - "id": "def-public.convertToBuildEsQuery.$1.filters", - "type": "Array", + "id": "def-public.getTableSkipFocus.$1.tableHasFocus", + "type": "Function", "tags": [], - "label": "filters", + "label": "tableHasFocus", "description": [], "signature": [ + "(containerElement: HTMLElement | null) => boolean" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]" + "parentPluginId": "timelines", + "id": "def-public.getTableSkipFocus.$1.tableHasFocus.$1", + "type": "CompoundType", + "tags": [], + "label": "containerElement", + "description": [], + "signature": [ + "HTMLElement | null" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", + "returnComment": [] + }, + { + "parentPluginId": "timelines", + "id": "def-public.getTableSkipFocus.$1.tableClassName", + "type": "string", + "tags": [], + "label": "tableClassName", + "description": [], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false } @@ -587,65 +483,131 @@ }, { "parentPluginId": "timelines", - "id": "def-public.createTGrid", + "id": "def-public.handleSkipFocus", "type": "Function", "tags": [], - "label": "createTGrid", - "description": [], + "label": "handleSkipFocus", + "description": [ + "\nIf the value of `skipFocus` is `SKIP_FOCUS_BACKWARDS` or `SKIP_FOCUS_FORWARD`\nthis function will invoke the provided `onSkipFocusBackwards` or\n`onSkipFocusForward` functions respectively.\n\nIf `skipFocus` is `SKIP_FOCUS_NOOP`, the `onSkipFocusBackwards` and\n`onSkipFocusForward` functions will not be invoked." + ], "signature": [ - "ActionCreator", - "<", - "TGridPersistInput", - ">" + "({ onSkipFocusBackwards, onSkipFocusForward, skipFocus, }: { onSkipFocusBackwards: () => void; onSkipFocusForward: () => void; skipFocus: ", + "SkipFocus", + "; }) => void" ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "timelines", - "id": "def-public.createTGrid.$1", - "type": "Uncategorized", + "id": "def-public.handleSkipFocus.$1", + "type": "Object", "tags": [], - "label": "payload", + "label": "{\n onSkipFocusBackwards,\n onSkipFocusForward,\n skipFocus,\n}", "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, - "trackAdoption": false - }, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-public.handleSkipFocus.$1.onSkipFocusBackwards", + "type": "Function", + "tags": [], + "label": "onSkipFocusBackwards", + "description": [], + "signature": [ + "() => void" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "timelines", + "id": "def-public.handleSkipFocus.$1.onSkipFocusForward", + "type": "Function", + "tags": [], + "label": "onSkipFocusForward", + "description": [], + "signature": [ + "() => void" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "timelines", + "id": "def-public.handleSkipFocus.$1.skipFocus", + "type": "CompoundType", + "tags": [], + "label": "skipFocus", + "description": [], + "signature": [ + "\"SKIP_FOCUS_BACKWARDS\" | \"SKIP_FOCUS_FORWARD\" | \"SKIP_FOCUS_NOOP\"" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.isArrowDownOrArrowUp", + "type": "Function", + "tags": [], + "label": "isArrowDownOrArrowUp", + "description": [ + "Returns `true` if the down or up arrow was pressed" + ], + "signature": [ + "(event: React.KeyboardEvent) => boolean" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "timelines", - "id": "def-public.createTGrid.$2", - "type": "CompoundType", + "id": "def-public.isArrowDownOrArrowUp.$1", + "type": "Object", "tags": [], - "label": "meta", + "label": "event", "description": [], "signature": [ - "Meta", - " | undefined" + "React.KeyboardEvent" ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true } ], + "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "timelines", - "id": "def-public.elementOrChildrenHasFocus", + "id": "def-public.isArrowUp", "type": "Function", "tags": [], - "label": "elementOrChildrenHasFocus", + "label": "isArrowUp", "description": [ - "Returns `true` when the element, or one of it's children has focus" + "Returns `true` if the up arrow key was pressed" ], "signature": [ - "(element: HTMLElement | null | undefined) => boolean" + "(event: React.KeyboardEvent) => boolean" ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, @@ -653,18 +615,18 @@ "children": [ { "parentPluginId": "timelines", - "id": "def-public.elementOrChildrenHasFocus.$1", - "type": "CompoundType", + "id": "def-public.isArrowUp.$1", + "type": "Object", "tags": [], - "label": "element", + "label": "event", "description": [], "signature": [ - "HTMLElement | null | undefined" + "React.KeyboardEvent" ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "isRequired": false + "isRequired": true } ], "returnComment": [], @@ -672,61 +634,66 @@ }, { "parentPluginId": "timelines", - "id": "def-public.escapeKuery", + "id": "def-public.isEscape", "type": "Function", "tags": [], - "label": "escapeKuery", - "description": [], + "label": "isEscape", + "description": [ + "Returns `true` if the escape key was pressed" + ], "signature": [ - "(val: string) => string" + "(event: React.KeyboardEvent) => boolean" ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "returnComment": [], "children": [ { "parentPluginId": "timelines", - "id": "def-public.escapeKuery.$1", - "type": "Uncategorized", + "id": "def-public.isEscape.$1", + "type": "Object", "tags": [], - "label": "args", + "label": "event", "description": [], "signature": [ - "A" + "React.KeyboardEvent" ], - "path": "node_modules/@types/lodash/ts3.1/fp.d.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "isRequired": true } ], + "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "timelines", - "id": "def-public.escapeQueryValue", + "id": "def-public.isTab", "type": "Function", "tags": [], - "label": "escapeQueryValue", - "description": [], + "label": "isTab", + "description": [ + "Returns `true` if the tab key was pressed" + ], "signature": [ - "(val?: string | number) => string | number" + "(event: React.KeyboardEvent) => boolean" ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "timelines", - "id": "def-public.escapeQueryValue.$1", - "type": "CompoundType", + "id": "def-public.isTab.$1", + "type": "Object", "tags": [], - "label": "val", + "label": "event", "description": [], "signature": [ - "string | number" + "React.KeyboardEvent" ], - "path": "x-pack/plugins/timelines/public/components/utils/keury/index.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -737,15 +704,17 @@ }, { "parentPluginId": "timelines", - "id": "def-public.focusColumn", + "id": "def-public.onKeyDownFocusHandler", "type": "Function", "tags": [], - "label": "focusColumn", + "label": "onKeyDownFocusHandler", "description": [ - "\nSIDE EFFECT: mutates the DOM by focusing the specified column\nreturns the `aria-colindex` of the newly-focused column" + "\nThis function adds keyboard accessability to any `containerElement` that\nrenders its rows with support for `aria-colindex` and `aria-rowindex`.\n\nTo use this function, invoke it in the `onKeyDown` handler of the specified\n`containerElement`.\n\nSee the `Keyboard Support` section of\nhttps://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html\nfor details of the behavior." ], "signature": [ - "({ colindexAttribute, containerElement, ariaColindex, ariaRowindex, rowindexAttribute, }: { colindexAttribute: string; containerElement: Element | null; ariaColindex: number; ariaRowindex: number; rowindexAttribute: string; }) => FocusColumnResult" + "({ colindexAttribute, containerElement, event, maxAriaColindex, maxAriaRowindex, onColumnFocused, rowindexAttribute, }: { colindexAttribute: string; containerElement: HTMLDivElement | null; event: React.KeyboardEvent; maxAriaColindex: number; maxAriaRowindex: number; onColumnFocused: ", + "OnColumnFocused", + "; rowindexAttribute: string; }) => void" ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, @@ -753,10 +722,10 @@ "children": [ { "parentPluginId": "timelines", - "id": "def-public.focusColumn.$1", + "id": "def-public.onKeyDownFocusHandler.$1", "type": "Object", "tags": [], - "label": "{\n colindexAttribute,\n containerElement,\n ariaColindex,\n ariaRowindex,\n rowindexAttribute,\n}", + "label": "{\n colindexAttribute,\n containerElement,\n event,\n maxAriaColindex,\n maxAriaRowindex,\n onColumnFocused,\n rowindexAttribute,\n}", "description": [], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, @@ -764,7 +733,7 @@ "children": [ { "parentPluginId": "timelines", - "id": "def-public.focusColumn.$1.colindexAttribute", + "id": "def-public.onKeyDownFocusHandler.$1.colindexAttribute", "type": "string", "tags": [], "label": "colindexAttribute", @@ -775,13 +744,13 @@ }, { "parentPluginId": "timelines", - "id": "def-public.focusColumn.$1.containerElement", + "id": "def-public.onKeyDownFocusHandler.$1.containerElement", "type": "CompoundType", "tags": [], "label": "containerElement", "description": [], "signature": [ - "Element | null" + "HTMLDivElement | null" ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, @@ -789,10 +758,24 @@ }, { "parentPluginId": "timelines", - "id": "def-public.focusColumn.$1.ariaColindex", + "id": "def-public.onKeyDownFocusHandler.$1.event", + "type": "Object", + "tags": [], + "label": "event", + "description": [], + "signature": [ + "React.KeyboardEvent" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.onKeyDownFocusHandler.$1.maxAriaColindex", "type": "number", "tags": [], - "label": "ariaColindex", + "label": "maxAriaColindex", "description": [], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, @@ -800,10 +783,10 @@ }, { "parentPluginId": "timelines", - "id": "def-public.focusColumn.$1.ariaRowindex", + "id": "def-public.onKeyDownFocusHandler.$1.maxAriaRowindex", "type": "number", "tags": [], - "label": "ariaRowindex", + "label": "maxAriaRowindex", "description": [], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, @@ -811,7 +794,38 @@ }, { "parentPluginId": "timelines", - "id": "def-public.focusColumn.$1.rowindexAttribute", + "id": "def-public.onKeyDownFocusHandler.$1.onColumnFocused", + "type": "Function", + "tags": [], + "label": "onColumnFocused", + "description": [], + "signature": [ + "({ newFocusedColumn, newFocusedColumnAriaColindex, }: { newFocusedColumn: HTMLDivElement | null; newFocusedColumnAriaColindex: number | null; }) => void" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "timelines", + "id": "def-public.onKeyDownFocusHandler.$1.onColumnFocused.$1", + "type": "Object", + "tags": [], + "label": "__0", + "description": [], + "signature": [ + "{ newFocusedColumn: HTMLDivElement | null; newFocusedColumnAriaColindex: number | null; }" + ], + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "timelines", + "id": "def-public.onKeyDownFocusHandler.$1.rowindexAttribute", "type": "string", "tags": [], "label": "rowindexAttribute", @@ -828,31 +842,31 @@ }, { "parentPluginId": "timelines", - "id": "def-public.getActionsColumnWidth", + "id": "def-public.stopPropagationAndPreventDefault", "type": "Function", "tags": [], - "label": "getActionsColumnWidth", + "label": "stopPropagationAndPreventDefault", "description": [ - "\nReturns the width of the Actions column based on the number of buttons being\ndisplayed\n\nNOTE: This function is necessary because `width` is a required property of\nthe `EuiDataGridControlColumn` interface, so it must be calculated before\ncontent is rendered. (The width of a `EuiDataGridControlColumn` does not\nautomatically size itself to fit all the content.)" + "\nThis function has side effects: It stops propagation of the provided\n`KeyboardEvent` and prevents the browser's default behavior." ], "signature": [ - "(actionButtonCount: number) => number" + "(event: React.KeyboardEvent) => void" ], - "path": "x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.tsx", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "timelines", - "id": "def-public.getActionsColumnWidth.$1", - "type": "number", + "id": "def-public.stopPropagationAndPreventDefault.$1", + "type": "Object", "tags": [], - "label": "actionButtonCount", + "label": "event", "description": [], "signature": [ - "number" + "React.KeyboardEvent" ], - "path": "x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.tsx", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -860,3379 +874,492 @@ ], "returnComment": [], "initialIsOpen": false - }, + } + ], + "interfaces": [ { "parentPluginId": "timelines", - "id": "def-public.getFocusedAriaColindexCell", - "type": "Function", + "id": "def-public.AddToTimelineButtonProps", + "type": "Interface", "tags": [], - "label": "getFocusedAriaColindexCell", - "description": [ - "\nReturns the focused cell for tables that use `aria-colindex`" - ], + "label": "AddToTimelineButtonProps", + "description": [], "signature": [ - "({ containerElement, tableClassName, }: { containerElement: HTMLElement | null; tableClassName: string; }) => HTMLDivElement | null" + { + "pluginId": "timelines", + "scope": "public", + "docId": "kibTimelinesPluginApi", + "section": "def-public.AddToTimelineButtonProps", + "text": "AddToTimelineButtonProps" + }, + " extends ", + "HoverActionComponentProps" ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "path": "x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "timelines", - "id": "def-public.getFocusedAriaColindexCell.$1", - "type": "Object", + "id": "def-public.AddToTimelineButtonProps.Component", + "type": "CompoundType", "tags": [], - "label": "{\n containerElement,\n tableClassName,\n}", + "label": "Component", + "description": [ + "`Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality" + ], + "signature": [ + "React.FunctionComponent<(", + "DisambiguateSet", + " & ", + "CommonEuiButtonEmptyProps", + " & { onClick?: React.MouseEventHandler | undefined; } & React.ButtonHTMLAttributes) | (", + "DisambiguateSet", + " & ", + "CommonEuiButtonEmptyProps", + " & { href?: string | undefined; onClick?: React.MouseEventHandler | undefined; } & React.AnchorHTMLAttributes)> | React.FunctionComponent<(", + "DisambiguateSet", + "<", + "EuiButtonIconPropsForAnchor", + ", ", + "EuiButtonIconPropsForButton", + "> & { type?: \"reset\" | \"button\" | \"submit\" | undefined; } & ", + "EuiButtonIconProps", + " & { onClick?: React.MouseEventHandler | undefined; } & React.ButtonHTMLAttributes & { buttonRef?: React.Ref | undefined; }) | (", + "DisambiguateSet", + "<", + "EuiButtonIconPropsForButton", + ", ", + "EuiButtonIconPropsForAnchor", + "> & { type?: string | undefined; } & ", + "EuiButtonIconProps", + " & { href?: string | undefined; onClick?: React.MouseEventHandler | undefined; } & React.AnchorHTMLAttributes & { buttonRef?: React.Ref | undefined; })> | typeof ", + "EuiContextMenuItem", + " | undefined" + ], + "path": "x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.AddToTimelineButtonProps.draggableId", + "type": "string", + "tags": [], + "label": "draggableId", "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx", "deprecated": false, - "trackAdoption": false, - "children": [ + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.AddToTimelineButtonProps.dataProvider", + "type": "CompoundType", + "tags": [], + "label": "dataProvider", + "description": [], + "signature": [ { - "parentPluginId": "timelines", - "id": "def-public.getFocusedAriaColindexCell.$1.containerElement", - "type": "CompoundType", - "tags": [], - "label": "containerElement", - "description": [], - "signature": [ - "HTMLElement | null" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "timelines", + "scope": "common", + "docId": "kibTimelinesPluginApi", + "section": "def-common.DataProvider", + "text": "DataProvider" }, + " | ", { - "parentPluginId": "timelines", - "id": "def-public.getFocusedAriaColindexCell.$1.tableClassName", - "type": "string", - "tags": [], - "label": "tableClassName", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - } - ] + "pluginId": "timelines", + "scope": "common", + "docId": "kibTimelinesPluginApi", + "section": "def-common.DataProvider", + "text": "DataProvider" + }, + "[] | undefined" + ], + "path": "x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-public.AddToTimelineButtonProps.timelineType", + "type": "string", + "tags": [], + "label": "timelineType", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx", + "deprecated": false, + "trackAdoption": false } ], - "returnComment": [], "initialIsOpen": false - }, + } + ], + "enums": [], + "misc": [ { "parentPluginId": "timelines", - "id": "def-public.getFocusedDataColindexCell", - "type": "Function", + "id": "def-public.ARIA_COLINDEX_ATTRIBUTE", + "type": "string", "tags": [], - "label": "getFocusedDataColindexCell", + "label": "ARIA_COLINDEX_ATTRIBUTE", "description": [ - "\nReturns the focused cell for tables that use `data-colindex`" + "\nThe name of the ARIA attribute representing a column, used in conjunction with\nthe ARIA: grid role https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html" ], "signature": [ - "({ containerElement, tableClassName, }: { containerElement: HTMLElement | null; tableClassName: string; }) => HTMLDivElement | null" + "\"aria-colindex\"" ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.getFocusedDataColindexCell.$1", - "type": "Object", - "tags": [], - "label": "{\n containerElement,\n tableClassName,\n}", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.getFocusedDataColindexCell.$1.containerElement", - "type": "CompoundType", - "tags": [], - "label": "containerElement", - "description": [], - "signature": [ - "HTMLElement | null" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.getFocusedDataColindexCell.$1.tableClassName", - "type": "string", - "tags": [], - "label": "tableClassName", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "timelines", - "id": "def-public.getNotesContainerClassName", - "type": "Function", + "id": "def-public.ARIA_ROWINDEX_ATTRIBUTE", + "type": "string", "tags": [], - "label": "getNotesContainerClassName", - "description": [], + "label": "ARIA_ROWINDEX_ATTRIBUTE", + "description": [ + "\nThe name of the ARIA attribute representing a row, used in conjunction with\nthe ARIA: grid role https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html" + ], "signature": [ - "(ariaRowindex: number) => string" + "\"aria-rowindex\"" ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.getNotesContainerClassName.$1", - "type": "number", - "tags": [], - "label": "ariaRowindex", - "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "timelines", - "id": "def-public.getPageRowIndex", - "type": "Function", + "id": "def-public.DATA_COLINDEX_ATTRIBUTE", + "type": "string", "tags": [], - "label": "getPageRowIndex", + "label": "DATA_COLINDEX_ATTRIBUTE", "description": [ - "\nrowIndex is bigger than `data.length` for pages with page numbers bigger than one.\nFor that reason, we must calculate `rowIndex % itemsPerPage`.\n\nEx:\nGiven `rowIndex` is `13` and `itemsPerPage` is `10`.\nIt means that the `activePage` is `2` and the `pageRowIndex` is `3`\n\n**Warning**:\nBe careful with array out of bounds. `pageRowIndex` can be bigger or equal to `data.length`\n in the scenario where the user changes the event status (Open, Acknowledged, Closed)." + "\nThis alternative attribute to `aria-colindex` is used to decorate the data\nin existing `EuiTable`s to enable keyboard navigation with minimal\nrefactoring of existing code until we're ready to migrate to `EuiDataGrid`.\nIt may be applied directly to keyboard-focusable elements and thus doesn't\nhave exactly the same semantics as `aria-colindex`." ], "signature": [ - "(rowIndex: number, itemsPerPage: number) => number" + "\"data-colindex\"" ], - "path": "x-pack/plugins/timelines/common/utils/pagination.ts", + "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.getPageRowIndex.$1", - "type": "number", - "tags": [], - "label": "rowIndex", - "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/timelines/common/utils/pagination.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "timelines", - "id": "def-public.getPageRowIndex.$2", - "type": "number", - "tags": [], - "label": "itemsPerPage", - "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/timelines/common/utils/pagination.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "timelines", - "id": "def-public.getRowRendererClassName", - "type": "Function", + "id": "def-public.DATA_ROWINDEX_ATTRIBUTE", + "type": "string", "tags": [], - "label": "getRowRendererClassName", - "description": [], + "label": "DATA_ROWINDEX_ATTRIBUTE", + "description": [ + "\nThis alternative attribute to `aria-rowindex` is used to decorate the data\nin existing `EuiTable`s to enable keyboard navigation with minimal\nrefactoring of existing code until we're ready to migrate to `EuiDataGrid`.\nIt's typically applied to `` elements via `EuiTable`'s `rowProps` prop." + ], "signature": [ - "(ariaRowindex: number) => string" + "\"data-rowindex\"" ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.getRowRendererClassName.$1", - "type": "number", - "tags": [], - "label": "ariaRowindex", - "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "timelines", - "id": "def-public.getTableSkipFocus", - "type": "Function", + "id": "def-public.FIRST_ARIA_INDEX", + "type": "number", "tags": [], - "label": "getTableSkipFocus", + "label": "FIRST_ARIA_INDEX", "description": [ - "\nThis function, which works with tables that use the `aria-colindex` or\n`data-colindex` attributes, examines the focus state of the table, and\nreturns a `SkipFocus` enumeration.\n\nThe `SkipFocus` return value indicates whether the caller should skip focus\nto \"before\" the table, \"after\" the table, or take no action, and let the\nbrowser's \"natural\" focus management manage focus." + "`aria-colindex` and `aria-rowindex` start at one" ], "signature": [ - "({ containerElement, getFocusedCell, shiftKey, tableHasFocus, tableClassName, }: { containerElement: HTMLElement | null; getFocusedCell: ", - "GetFocusedCell", - "; shiftKey: boolean; tableHasFocus: (containerElement: HTMLElement | null) => boolean; tableClassName: string; }) => ", - "SkipFocus" + "1" ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.getTableSkipFocus.$1", - "type": "Object", - "tags": [], - "label": "{\n containerElement,\n getFocusedCell,\n shiftKey,\n tableHasFocus,\n tableClassName,\n}", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.getTableSkipFocus.$1.containerElement", - "type": "CompoundType", - "tags": [], - "label": "containerElement", - "description": [], - "signature": [ - "HTMLElement | null" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.getTableSkipFocus.$1.getFocusedCell", - "type": "Function", - "tags": [], - "label": "getFocusedCell", - "description": [], - "signature": [ - "({ containerElement, tableClassName, }: { containerElement: HTMLElement | null; tableClassName: string; }) => HTMLDivElement | null" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.getTableSkipFocus.$1.getFocusedCell.$1", - "type": "Object", - "tags": [], - "label": "__0", - "description": [], - "signature": [ - "{ containerElement: HTMLElement | null; tableClassName: string; }" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "timelines", - "id": "def-public.getTableSkipFocus.$1.shiftKey", - "type": "boolean", - "tags": [], - "label": "shiftKey", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.getTableSkipFocus.$1.tableHasFocus", - "type": "Function", - "tags": [], - "label": "tableHasFocus", - "description": [], - "signature": [ - "(containerElement: HTMLElement | null) => boolean" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.getTableSkipFocus.$1.tableHasFocus.$1", - "type": "CompoundType", - "tags": [], - "label": "containerElement", - "description": [], - "signature": [ - "HTMLElement | null" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.getTableSkipFocus.$1.tableClassName", - "type": "string", - "tags": [], - "label": "tableClassName", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [], "initialIsOpen": false }, { "parentPluginId": "timelines", - "id": "def-public.getTimelineIdFromColumnDroppableId", - "type": "Function", + "id": "def-public.OnColumnFocused", + "type": "Type", "tags": [], - "label": "getTimelineIdFromColumnDroppableId", + "label": "OnColumnFocused", "description": [], "signature": [ - "(droppableId: string) => string" - ], - "path": "x-pack/plugins/timelines/public/components/drag_and_drop/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.getTimelineIdFromColumnDroppableId.$1", - "type": "string", - "tags": [], - "label": "droppableId", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/timelines/public/components/drag_and_drop/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.handleSkipFocus", - "type": "Function", - "tags": [], - "label": "handleSkipFocus", - "description": [ - "\nIf the value of `skipFocus` is `SKIP_FOCUS_BACKWARDS` or `SKIP_FOCUS_FORWARD`\nthis function will invoke the provided `onSkipFocusBackwards` or\n`onSkipFocusForward` functions respectively.\n\nIf `skipFocus` is `SKIP_FOCUS_NOOP`, the `onSkipFocusBackwards` and\n`onSkipFocusForward` functions will not be invoked." - ], - "signature": [ - "({ onSkipFocusBackwards, onSkipFocusForward, skipFocus, }: { onSkipFocusBackwards: () => void; onSkipFocusForward: () => void; skipFocus: ", - "SkipFocus", - "; }) => void" + "({ newFocusedColumn, newFocusedColumnAriaColindex, }: { newFocusedColumn: HTMLDivElement | null; newFocusedColumnAriaColindex: number | null; }) => void" ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, "trackAdoption": false, + "returnComment": [], "children": [ { "parentPluginId": "timelines", - "id": "def-public.handleSkipFocus.$1", + "id": "def-public.OnColumnFocused.$1", "type": "Object", "tags": [], - "label": "{\n onSkipFocusBackwards,\n onSkipFocusForward,\n skipFocus,\n}", + "label": "__0", "description": [], + "signature": [ + "{ newFocusedColumn: HTMLDivElement | null; newFocusedColumnAriaColindex: number | null; }" + ], "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.handleSkipFocus.$1.onSkipFocusBackwards", - "type": "Function", - "tags": [], - "label": "onSkipFocusBackwards", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.handleSkipFocus.$1.onSkipFocusForward", - "type": "Function", - "tags": [], - "label": "onSkipFocusForward", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.handleSkipFocus.$1.skipFocus", - "type": "CompoundType", - "tags": [], - "label": "skipFocus", - "description": [], - "signature": [ - "\"SKIP_FOCUS_BACKWARDS\" | \"SKIP_FOCUS_FORWARD\" | \"SKIP_FOCUS_NOOP\"" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - } - ] + "trackAdoption": false } ], - "returnComment": [], "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.initializeTGridSettings", - "type": "Function", - "tags": [], - "label": "initializeTGridSettings", - "description": [], - "signature": [ - "ActionCreator", - "<", - "InitialyzeTGridSettings", - ">" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.initializeTGridSettings.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.initializeTGridSettings.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.isArrowDownOrArrowUp", - "type": "Function", - "tags": [], - "label": "isArrowDownOrArrowUp", - "description": [ - "Returns `true` if the down or up arrow was pressed" - ], - "signature": [ - "(event: React.KeyboardEvent) => boolean" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.isArrowDownOrArrowUp.$1", - "type": "Object", - "tags": [], - "label": "event", - "description": [], - "signature": [ - "React.KeyboardEvent" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.isArrowUp", - "type": "Function", - "tags": [], - "label": "isArrowUp", - "description": [ - "Returns `true` if the up arrow key was pressed" - ], - "signature": [ - "(event: React.KeyboardEvent) => boolean" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.isArrowUp.$1", - "type": "Object", - "tags": [], - "label": "event", - "description": [], - "signature": [ - "React.KeyboardEvent" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.isEscape", - "type": "Function", - "tags": [], - "label": "isEscape", - "description": [ - "Returns `true` if the escape key was pressed" - ], - "signature": [ - "(event: React.KeyboardEvent) => boolean" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.isEscape.$1", - "type": "Object", - "tags": [], - "label": "event", - "description": [], - "signature": [ - "React.KeyboardEvent" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.isTab", - "type": "Function", - "tags": [], - "label": "isTab", - "description": [ - "Returns `true` if the tab key was pressed" - ], - "signature": [ - "(event: React.KeyboardEvent) => boolean" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.isTab.$1", - "type": "Object", - "tags": [], - "label": "event", - "description": [], - "signature": [ - "React.KeyboardEvent" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.onKeyDownFocusHandler", - "type": "Function", - "tags": [], - "label": "onKeyDownFocusHandler", - "description": [ - "\nThis function adds keyboard accessability to any `containerElement` that\nrenders its rows with support for `aria-colindex` and `aria-rowindex`.\n\nTo use this function, invoke it in the `onKeyDown` handler of the specified\n`containerElement`.\n\nSee the `Keyboard Support` section of\nhttps://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html\nfor details of the behavior." - ], - "signature": [ - "({ colindexAttribute, containerElement, event, maxAriaColindex, maxAriaRowindex, onColumnFocused, rowindexAttribute, }: { colindexAttribute: string; containerElement: HTMLDivElement | null; event: React.KeyboardEvent; maxAriaColindex: number; maxAriaRowindex: number; onColumnFocused: ", - "OnColumnFocused", - "; rowindexAttribute: string; }) => void" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.onKeyDownFocusHandler.$1", - "type": "Object", - "tags": [], - "label": "{\n colindexAttribute,\n containerElement,\n event,\n maxAriaColindex,\n maxAriaRowindex,\n onColumnFocused,\n rowindexAttribute,\n}", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.onKeyDownFocusHandler.$1.colindexAttribute", - "type": "string", - "tags": [], - "label": "colindexAttribute", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.onKeyDownFocusHandler.$1.containerElement", - "type": "CompoundType", - "tags": [], - "label": "containerElement", - "description": [], - "signature": [ - "HTMLDivElement | null" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.onKeyDownFocusHandler.$1.event", - "type": "Object", - "tags": [], - "label": "event", - "description": [], - "signature": [ - "React.KeyboardEvent" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.onKeyDownFocusHandler.$1.maxAriaColindex", - "type": "number", - "tags": [], - "label": "maxAriaColindex", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.onKeyDownFocusHandler.$1.maxAriaRowindex", - "type": "number", - "tags": [], - "label": "maxAriaRowindex", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.onKeyDownFocusHandler.$1.onColumnFocused", - "type": "Function", - "tags": [], - "label": "onColumnFocused", - "description": [], - "signature": [ - "({ newFocusedColumn, newFocusedColumnAriaColindex, }: { newFocusedColumn: HTMLDivElement | null; newFocusedColumnAriaColindex: number | null; }) => void" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.onKeyDownFocusHandler.$1.onColumnFocused.$1", - "type": "Object", - "tags": [], - "label": "__0", - "description": [], - "signature": [ - "{ newFocusedColumn: HTMLDivElement | null; newFocusedColumnAriaColindex: number | null; }" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "timelines", - "id": "def-public.onKeyDownFocusHandler.$1.rowindexAttribute", - "type": "string", - "tags": [], - "label": "rowindexAttribute", - "description": [], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.removeColumn", - "type": "Function", - "tags": [], - "label": "removeColumn", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; columnId: string; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.removeColumn.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.removeColumn.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.setEventsDeleted", - "type": "Function", - "tags": [], - "label": "setEventsDeleted", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; eventIds: string[]; isDeleted: boolean; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.setEventsDeleted.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.setEventsDeleted.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.setEventsLoading", - "type": "Function", - "tags": [], - "label": "setEventsLoading", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; eventIds: string[]; isLoading: boolean; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.setEventsLoading.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.setEventsLoading.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.setSelected", - "type": "Function", - "tags": [], - "label": "setSelected", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; eventIds: Readonly>; isSelected: boolean; isSelectAllChecked: boolean; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.setSelected.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.setSelected.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.setTGridSelectAll", - "type": "Function", - "tags": [], - "label": "setTGridSelectAll", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; selectAll: boolean; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.setTGridSelectAll.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.setTGridSelectAll.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.stopPropagationAndPreventDefault", - "type": "Function", - "tags": [], - "label": "stopPropagationAndPreventDefault", - "description": [ - "\nThis function has side effects: It stops propagation of the provided\n`KeyboardEvent` and prevents the browser's default behavior." - ], - "signature": [ - "(event: React.KeyboardEvent) => void" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.stopPropagationAndPreventDefault.$1", - "type": "Object", - "tags": [], - "label": "event", - "description": [], - "signature": [ - "React.KeyboardEvent" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.tGridReducer", - "type": "Function", - "tags": [], - "label": "tGridReducer", - "description": [ - "The reducer for all data table actions" - ], - "signature": [ - "(state: ", - { - "pluginId": "timelines", - "scope": "public", - "docId": "kibTimelinesPluginApi", - "section": "def-public.TableState", - "text": "TableState" - }, - " | undefined, action: { type: any; }) => ", - { - "pluginId": "timelines", - "scope": "public", - "docId": "kibTimelinesPluginApi", - "section": "def-public.TableState", - "text": "TableState" - } - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/reducer.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.tGridReducer.$1", - "type": "Uncategorized", - "tags": [], - "label": "state", - "description": [], - "signature": [ - "PassedS" - ], - "path": "node_modules/typescript-fsa-reducers/dist/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.tGridReducer.$2", - "type": "Object", - "tags": [], - "label": "action", - "description": [], - "signature": [ - "{ type: any; }" - ], - "path": "node_modules/typescript-fsa-reducers/dist/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.toggleDetailPanel", - "type": "Function", - "tags": [], - "label": "toggleDetailPanel", - "description": [], - "signature": [ - "ActionCreator", - "<", - "TableToggleDetailPanel", - ">" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.toggleDetailPanel.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.toggleDetailPanel.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateColumnOrder", - "type": "Function", - "tags": [], - "label": "updateColumnOrder", - "description": [], - "signature": [ - "ActionCreator", - "<{ columnIds: string[]; id: string; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.updateColumnOrder.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateColumnOrder.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateColumns", - "type": "Function", - "tags": [], - "label": "updateColumns", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; columns: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderOptions", - "text": "ColumnHeaderOptions" - }, - "[]; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.updateColumns.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateColumns.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateColumnWidth", - "type": "Function", - "tags": [], - "label": "updateColumnWidth", - "description": [], - "signature": [ - "ActionCreator", - "<{ columnId: string; id: string; width: number; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.updateColumnWidth.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateColumnWidth.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateGraphEventId", - "type": "Function", - "tags": [], - "label": "updateGraphEventId", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; graphEventId: string; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.updateGraphEventId.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateGraphEventId.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateIsLoading", - "type": "Function", - "tags": [], - "label": "updateIsLoading", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; isLoading: boolean; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.updateIsLoading.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateIsLoading.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateItemsPerPage", - "type": "Function", - "tags": [], - "label": "updateItemsPerPage", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; itemsPerPage: number; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.updateItemsPerPage.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateItemsPerPage.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateItemsPerPageOptions", - "type": "Function", - "tags": [], - "label": "updateItemsPerPageOptions", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; itemsPerPageOptions: number[]; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.updateItemsPerPageOptions.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateItemsPerPageOptions.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateSessionViewConfig", - "type": "Function", - "tags": [], - "label": "updateSessionViewConfig", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; sessionViewConfig: ", - "SessionViewConfig", - " | null; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.updateSessionViewConfig.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateSessionViewConfig.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateSort", - "type": "Function", - "tags": [], - "label": "updateSort", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; sort: ", - "SortColumnTable", - "[]; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.updateSort.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateSort.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateTotalCount", - "type": "Function", - "tags": [], - "label": "updateTotalCount", - "description": [], - "signature": [ - "ActionCreator", - "<{ id: string; totalCount: number; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.updateTotalCount.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.updateTotalCount.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.upsertColumn", - "type": "Function", - "tags": [], - "label": "upsertColumn", - "description": [], - "signature": [ - "ActionCreator", - "<{ column: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderOptions", - "text": "ColumnHeaderOptions" - }, - "; id: string; index: number; }>" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/actions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.upsertColumn.$1", - "type": "Uncategorized", - "tags": [], - "label": "payload", - "description": [], - "signature": [ - "Payload" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.upsertColumn.$2", - "type": "CompoundType", - "tags": [], - "label": "meta", - "description": [], - "signature": [ - "Meta", - " | undefined" - ], - "path": "node_modules/typescript-fsa/lib/index.d.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.useBulkActionItems", - "type": "Function", - "tags": [], - "label": "useBulkActionItems", - "description": [], - "signature": [ - "({ eventIds, currentStatus, query, indexName, setEventsLoading, showAlertStatusActions, setEventsDeleted, onUpdateSuccess, onUpdateFailure, customBulkActions, }: ", - "BulkActionsProps", - ") => JSX.Element[]" - ], - "path": "x-pack/plugins/timelines/public/hooks/use_bulk_action_items.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.useBulkActionItems.$1", - "type": "Object", - "tags": [], - "label": "{\n eventIds,\n currentStatus,\n query,\n indexName,\n setEventsLoading,\n showAlertStatusActions = true,\n setEventsDeleted,\n onUpdateSuccess,\n onUpdateFailure,\n customBulkActions,\n}", - "description": [], - "signature": [ - "BulkActionsProps" - ], - "path": "x-pack/plugins/timelines/public/hooks/use_bulk_action_items.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - } - ], - "interfaces": [ - { - "parentPluginId": "timelines", - "id": "def-public.AddToTimelineButtonProps", - "type": "Interface", - "tags": [], - "label": "AddToTimelineButtonProps", - "description": [], - "signature": [ - { - "pluginId": "timelines", - "scope": "public", - "docId": "kibTimelinesPluginApi", - "section": "def-public.AddToTimelineButtonProps", - "text": "AddToTimelineButtonProps" - }, - " extends ", - "HoverActionComponentProps" - ], - "path": "x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.AddToTimelineButtonProps.Component", - "type": "CompoundType", - "tags": [], - "label": "Component", - "description": [ - "`Component` is only used with `EuiDataGrid`; the grid keeps a reference to `Component` for show / hide functionality" - ], - "signature": [ - "React.FunctionComponent<(", - "DisambiguateSet", - " & ", - "CommonEuiButtonEmptyProps", - " & { onClick?: React.MouseEventHandler | undefined; } & React.ButtonHTMLAttributes) | (", - "DisambiguateSet", - " & ", - "CommonEuiButtonEmptyProps", - " & { href?: string | undefined; onClick?: React.MouseEventHandler | undefined; } & React.AnchorHTMLAttributes)> | React.FunctionComponent<(", - "DisambiguateSet", - "<", - "EuiButtonIconPropsForAnchor", - ", ", - "EuiButtonIconPropsForButton", - "> & { type?: \"reset\" | \"button\" | \"submit\" | undefined; } & ", - "EuiButtonIconProps", - " & { onClick?: React.MouseEventHandler | undefined; } & React.ButtonHTMLAttributes & { buttonRef?: React.Ref | undefined; }) | (", - "DisambiguateSet", - "<", - "EuiButtonIconPropsForButton", - ", ", - "EuiButtonIconPropsForAnchor", - "> & { type?: string | undefined; } & ", - "EuiButtonIconProps", - " & { href?: string | undefined; onClick?: React.MouseEventHandler | undefined; } & React.AnchorHTMLAttributes & { buttonRef?: React.Ref | undefined; })> | typeof ", - "EuiContextMenuItem", - " | undefined" - ], - "path": "x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.AddToTimelineButtonProps.draggableId", - "type": "string", - "tags": [], - "label": "draggableId", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.AddToTimelineButtonProps.dataProvider", - "type": "CompoundType", - "tags": [], - "label": "dataProvider", - "description": [], - "signature": [ - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.DataProvider", - "text": "DataProvider" - }, - " | ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.DataProvider", - "text": "DataProvider" - }, - "[] | undefined" - ], - "path": "x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.AddToTimelineButtonProps.timelineType", - "type": "string", - "tags": [], - "label": "timelineType", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/timelines/public/components/hover_actions/actions/add_to_timeline.tsx", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TableById", - "type": "Interface", - "tags": [], - "label": "TableById", - "description": [ - "A map of id to data table" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.TableById.Unnamed", - "type": "IndexSignature", - "tags": [], - "label": "[id: string]: TGridModel", - "description": [], - "signature": [ - "[id: string]: ", - { - "pluginId": "timelines", - "scope": "public", - "docId": "kibTimelinesPluginApi", - "section": "def-public.TGridModel", - "text": "TGridModel" - } - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TableState", - "type": "Interface", - "tags": [], - "label": "TableState", - "description": [ - "The state of all data tables is stored here" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.TableState.tableById", - "type": "Object", - "tags": [], - "label": "tableById", - "description": [], - "signature": [ - { - "pluginId": "timelines", - "scope": "public", - "docId": "kibTimelinesPluginApi", - "section": "def-public.TableById", - "text": "TableById" - } - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel", - "type": "Interface", - "tags": [], - "label": "TGridModel", - "description": [], - "signature": [ - { - "pluginId": "timelines", - "scope": "public", - "docId": "kibTimelinesPluginApi", - "section": "def-public.TGridModel", - "text": "TGridModel" - }, - " extends ", - "TGridModelSettings" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.columns", - "type": "Array", - "tags": [], - "label": "columns", - "description": [ - "The columns displayed in the data table" - ], - "signature": [ - "(Pick<", - "EuiDataGridColumn", - ", \"id\" | \"display\" | \"displayAsText\" | \"initialWidth\"> & Pick<", - "EuiDataGridColumn", - ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", - "TGridCellAction", - "[] | undefined; category?: string | undefined; columnHeaderType: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderType", - "text": "ColumnHeaderType" - }, - "; description?: string | null | undefined; esTypes?: string[] | undefined; example?: string | number | null | undefined; format?: string | undefined; linkField?: string | undefined; placeholder?: string | undefined; subType?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.IFieldSubType", - "text": "IFieldSubType" - }, - " | undefined; type?: string | undefined; })[]" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.dataViewId", - "type": "CompoundType", - "tags": [], - "label": "dataViewId", - "description": [ - "Kibana data view id" - ], - "signature": [ - "string | null" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.deletedEventIds", - "type": "Array", - "tags": [], - "label": "deletedEventIds", - "description": [ - "Events to not be rendered" - ], - "signature": [ - "string[]" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.expandedDetail", - "type": "Object", - "tags": [], - "label": "expandedDetail", - "description": [ - "This holds the view information for the flyout when viewing data in a consuming view (i.e. hosts page) or the side panel in the primary data view" - ], - "signature": [ - "{ [x: string]: ", - "DataExpandedDetailType", - " | undefined; }" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.filters", - "type": "Array", - "tags": [], - "label": "filters", - "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[] | undefined" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.graphEventId", - "type": "string", - "tags": [], - "label": "graphEventId", - "description": [ - "When non-empty, display a graph view for this event" - ], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.id", - "type": "string", - "tags": [], - "label": "id", - "description": [ - "Uniquely identifies the data table" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.indexNames", - "type": "Array", - "tags": [], - "label": "indexNames", - "description": [], - "signature": [ - "string[]" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.isLoading", - "type": "boolean", - "tags": [], - "label": "isLoading", - "description": [], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.isSelectAllChecked", - "type": "boolean", - "tags": [], - "label": "isSelectAllChecked", - "description": [ - "If selectAll checkbox in header is checked" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.itemsPerPage", - "type": "number", - "tags": [], - "label": "itemsPerPage", - "description": [ - "The number of items to show in a single page of results" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.itemsPerPageOptions", - "type": "Array", - "tags": [], - "label": "itemsPerPageOptions", - "description": [ - "Displays a series of choices that when selected, become the value of `itemsPerPage`" - ], - "signature": [ - "number[]" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.loadingEventIds", - "type": "Array", - "tags": [], - "label": "loadingEventIds", - "description": [ - "Events to be rendered as loading" - ], - "signature": [ - "string[]" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.selectedEventIds", - "type": "Object", - "tags": [], - "label": "selectedEventIds", - "description": [ - "Events selected on this timeline -- eventId to TimelineNonEcsData[] mapping of data required for bulk actions" - ], - "signature": [ - "{ [x: string]: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.TimelineNonEcsData", - "text": "TimelineNonEcsData" - }, - "[]; }" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.initialized", - "type": "CompoundType", - "tags": [], - "label": "initialized", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.sessionViewConfig", - "type": "CompoundType", - "tags": [], - "label": "sessionViewConfig", - "description": [], - "signature": [ - "SessionViewConfig", - " | null" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.updated", - "type": "number", - "tags": [], - "label": "updated", - "description": [ - "updated saved object timestamp" - ], - "signature": [ - "number | undefined" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridModel.totalCount", - "type": "number", - "tags": [], - "label": "totalCount", - "description": [ - "Total number of fetched events/alerts" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - } - ], - "enums": [], - "misc": [ - { - "parentPluginId": "timelines", - "id": "def-public.ARIA_COLINDEX_ATTRIBUTE", - "type": "string", - "tags": [], - "label": "ARIA_COLINDEX_ATTRIBUTE", - "description": [ - "\nThe name of the ARIA attribute representing a column, used in conjunction with\nthe ARIA: grid role https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html" - ], - "signature": [ - "\"aria-colindex\"" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.ARIA_ROWINDEX_ATTRIBUTE", - "type": "string", - "tags": [], - "label": "ARIA_ROWINDEX_ATTRIBUTE", - "description": [ - "\nThe name of the ARIA attribute representing a row, used in conjunction with\nthe ARIA: grid role https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html" - ], - "signature": [ - "\"aria-rowindex\"" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.DATA_COLINDEX_ATTRIBUTE", - "type": "string", - "tags": [], - "label": "DATA_COLINDEX_ATTRIBUTE", - "description": [ - "\nThis alternative attribute to `aria-colindex` is used to decorate the data\nin existing `EuiTable`s to enable keyboard navigation with minimal\nrefactoring of existing code until we're ready to migrate to `EuiDataGrid`.\nIt may be applied directly to keyboard-focusable elements and thus doesn't\nhave exactly the same semantics as `aria-colindex`." - ], - "signature": [ - "\"data-colindex\"" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.DATA_ROWINDEX_ATTRIBUTE", - "type": "string", - "tags": [], - "label": "DATA_ROWINDEX_ATTRIBUTE", - "description": [ - "\nThis alternative attribute to `aria-rowindex` is used to decorate the data\nin existing `EuiTable`s to enable keyboard navigation with minimal\nrefactoring of existing code until we're ready to migrate to `EuiDataGrid`.\nIt's typically applied to `` elements via `EuiTable`'s `rowProps` prop." - ], - "signature": [ - "\"data-rowindex\"" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.DEFAULT_ACTION_BUTTON_WIDTH", - "type": "number", - "tags": [], - "label": "DEFAULT_ACTION_BUTTON_WIDTH", - "description": [ - "\nThis is the effective width in pixels of an action button used with\n`EuiDataGrid` `leadingControlColumns`. (See Notes below for details)\n\nNotes:\n1) This constant is necessary because `width` is a required property of\n the `EuiDataGridControlColumn` interface, so it must be calculated before\n content is rendered. (The width of a `EuiDataGridControlColumn` does not\n automatically size itself to fit all the content.)\n\n2) This is the *effective* width, because at the time of this writing,\n `EuiButtonIcon` has a `margin-left: -4px`, which is subtracted from the\n `width`" - ], - "path": "x-pack/plugins/timelines/public/components/t_grid/body/constants.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.FIRST_ARIA_INDEX", - "type": "number", - "tags": [], - "label": "FIRST_ARIA_INDEX", - "description": [ - "`aria-colindex` and `aria-rowindex` start at one" - ], - "signature": [ - "1" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.OnColumnFocused", - "type": "Type", - "tags": [], - "label": "OnColumnFocused", - "description": [], - "signature": [ - "({ newFocusedColumn, newFocusedColumnAriaColindex, }: { newFocusedColumn: HTMLDivElement | null; newFocusedColumnAriaColindex: number | null; }) => void" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.OnColumnFocused.$1", - "type": "Object", - "tags": [], - "label": "__0", - "description": [], - "signature": [ - "{ newFocusedColumn: HTMLDivElement | null; newFocusedColumnAriaColindex: number | null; }" - ], - "path": "x-pack/plugins/timelines/common/utils/accessibility/helpers.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.SortDirection", - "type": "Type", - "tags": [], - "label": "SortDirection", - "description": [], - "signature": [ - "\"none\" | \"asc\" | \"desc\" | ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.Direction", - "text": "Direction" - } - ], - "path": "x-pack/plugins/timelines/common/types/timeline/store.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.State", - "type": "Type", - "tags": [], - "label": "State", - "description": [], - "signature": [ - "EmptyObject", - " & ", - { - "pluginId": "timelines", - "scope": "public", - "docId": "kibTimelinesPluginApi", - "section": "def-public.TableState", - "text": "TableState" - } - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.SubsetTGridModel", - "type": "Type", - "tags": [], - "label": "SubsetTGridModel", - "description": [], - "signature": [ - "{ readonly columns: (Pick<", - "EuiDataGridColumn", - ", \"id\" | \"display\" | \"displayAsText\" | \"initialWidth\"> & Pick<", - "EuiDataGridColumn", - ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", - "TGridCellAction", - "[] | undefined; category?: string | undefined; columnHeaderType: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderType", - "text": "ColumnHeaderType" - }, - "; description?: string | null | undefined; esTypes?: string[] | undefined; example?: string | number | null | undefined; format?: string | undefined; linkField?: string | undefined; placeholder?: string | undefined; subType?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.IFieldSubType", - "text": "IFieldSubType" - }, - " | undefined; type?: string | undefined; })[]; readonly title?: string | undefined; readonly filters?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[] | undefined; readonly dataViewId: string | null; readonly sort: ", - "SortColumnTable", - "[]; readonly defaultColumns: (Pick<", - "EuiDataGridColumn", - ", \"id\" | \"display\" | \"displayAsText\" | \"initialWidth\"> & Pick<", - "EuiDataGridColumn", - ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", - "TGridCellAction", - "[] | undefined; category?: string | undefined; columnHeaderType: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderType", - "text": "ColumnHeaderType" - }, - "; description?: string | null | undefined; esTypes?: string[] | undefined; example?: string | number | null | undefined; format?: string | undefined; linkField?: string | undefined; placeholder?: string | undefined; subType?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.IFieldSubType", - "text": "IFieldSubType" - }, - " | undefined; type?: string | undefined; })[]; readonly isLoading: boolean; readonly queryFields: string[]; readonly selectAll: boolean; readonly showCheckboxes: boolean; readonly deletedEventIds: string[]; readonly expandedDetail: Partial>; readonly graphEventId?: string | undefined; readonly indexNames: string[]; readonly isSelectAllChecked: boolean; readonly itemsPerPage: number; readonly itemsPerPageOptions: number[]; readonly loadingEventIds: string[]; readonly selectedEventIds: Record; readonly sessionViewConfig: ", - "SessionViewConfig", - " | null; readonly totalCount: number; }" - ], - "path": "x-pack/plugins/timelines/public/store/t_grid/model.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TGridType", - "type": "Type", - "tags": [], - "label": "TGridType", - "description": [], - "signature": [ - "\"embedded\"" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], - "objects": [ - { - "parentPluginId": "timelines", - "id": "def-public.StatefulEventContext", - "type": "Object", - "tags": [], - "label": "StatefulEventContext", - "description": [], - "signature": [ - "React.Context<", - "StatefulEventContextType", - " | null>" - ], - "path": "x-pack/plugins/timelines/public/components/stateful_event_context.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-public.TableContext", - "type": "Object", - "tags": [], - "label": "TableContext", - "description": [], - "signature": [ - "React.Context<{ tableId: string | null; }>" - ], - "path": "x-pack/plugins/timelines/public/components/t_grid/shared/index.tsx", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], - "start": { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart", - "type": "Interface", - "tags": [], - "label": "TimelinesUIStart", - "description": [], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getHoverActions", - "type": "Function", - "tags": [], - "label": "getHoverActions", - "description": [], - "signature": [ - "() => ", - "HoverActionsConfig" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getTGrid", - "type": "Function", - "tags": [], - "label": "getTGrid", - "description": [], - "signature": [ - "(props: ", - "GetTGridProps", - ") => React.ReactElement<", - "GetTGridProps", - ", string | React.JSXElementConstructor>" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getTGrid.$1", - "type": "Uncategorized", - "tags": [], - "label": "props", - "description": [], - "signature": [ - "GetTGridProps", - "" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getTGridReducer", - "type": "Function", - "tags": [], - "label": "getTGridReducer", - "description": [], - "signature": [ - "() => any" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getTimelineReducer", - "type": "Function", - "tags": [], - "label": "getTimelineReducer", - "description": [], - "signature": [ - "() => any" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getLoadingPanel", - "type": "Function", - "tags": [], - "label": "getLoadingPanel", - "description": [], - "signature": [ - "(props: ", - "LoadingPanelProps", - ") => React.ReactElement<", - "LoadingPanelProps", - ", string | React.JSXElementConstructor>" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getLoadingPanel.$1", - "type": "Object", - "tags": [], - "label": "props", - "description": [], - "signature": [ - "LoadingPanelProps" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getLastUpdated", - "type": "Function", - "tags": [], - "label": "getLastUpdated", - "description": [], - "signature": [ - "(props: ", - "LastUpdatedAtProps", - ") => React.ReactElement<", - "LastUpdatedAtProps", - ", string | React.JSXElementConstructor>" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getLastUpdated.$1", - "type": "Object", - "tags": [], - "label": "props", - "description": [], - "signature": [ - "LastUpdatedAtProps" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getUseAddToTimeline", - "type": "Function", - "tags": [], - "label": "getUseAddToTimeline", - "description": [], - "signature": [ - "() => (props: ", - "UseAddToTimelineProps", - ") => ", - "UseAddToTimeline" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getUseAddToTimelineSensor", - "type": "Function", - "tags": [], - "label": "getUseAddToTimelineSensor", - "description": [], - "signature": [ - "() => (api: ", - "SensorAPI", - ") => void" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.getUseDraggableKeyboardWrapper", - "type": "Function", - "tags": [], - "label": "getUseDraggableKeyboardWrapper", - "description": [], - "signature": [ - "() => (props: ", - "UseDraggableKeyboardWrapperProps", - ") => ", - "UseDraggableKeyboardWrapper" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.setTGridEmbeddedStore", - "type": "Function", - "tags": [], - "label": "setTGridEmbeddedStore", - "description": [], - "signature": [ - "(store: ", - "Store", - ") => void" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-public.TimelinesUIStart.setTGridEmbeddedStore.$1", - "type": "Object", - "tags": [], - "label": "store", - "description": [], - "signature": [ - "Store", - "" - ], - "path": "x-pack/plugins/timelines/public/types.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - } - ], - "lifecycle": "start", - "initialIsOpen": true - } - }, - "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [], - "setup": { - "parentPluginId": "timelines", - "id": "def-server.TimelinesPluginUI", - "type": "Interface", - "tags": [], - "label": "TimelinesPluginUI", - "description": [], - "path": "x-pack/plugins/timelines/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "lifecycle": "setup", - "initialIsOpen": true - }, - "start": { - "parentPluginId": "timelines", - "id": "def-server.TimelinesPluginStart", - "type": "Interface", - "tags": [], - "label": "TimelinesPluginStart", - "description": [], - "path": "x-pack/plugins/timelines/server/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "lifecycle": "start", - "initialIsOpen": true - } - }, - "common": { - "classes": [], - "functions": [], - "interfaces": [ - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps", - "type": "Interface", - "tags": [], - "label": "ActionProps", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.action", - "type": "CompoundType", - "tags": [], - "label": "action", - "description": [], - "signature": [ - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.RowCellRender", - "text": "RowCellRender" - }, - " | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.ariaRowindex", - "type": "number", - "tags": [], - "label": "ariaRowindex", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.checked", - "type": "boolean", - "tags": [], - "label": "checked", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.columnId", - "type": "string", - "tags": [], - "label": "columnId", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.columnValues", - "type": "string", - "tags": [], - "label": "columnValues", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.data", - "type": "Array", - "tags": [], - "label": "data", - "description": [], - "signature": [ - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.TimelineNonEcsData", - "text": "TimelineNonEcsData" - }, - "[]" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.disabled", - "type": "CompoundType", - "tags": [], - "label": "disabled", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.ecsData", - "type": "Object", - "tags": [], - "label": "ecsData", - "description": [], - "signature": [ - "Ecs" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.eventId", - "type": "string", - "tags": [], - "label": "eventId", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.eventIdToNoteIds", - "type": "Object", - "tags": [], - "label": "eventIdToNoteIds", - "description": [], - "signature": [ - "Readonly> | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.index", - "type": "number", - "tags": [], - "label": "index", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.isEventPinned", - "type": "CompoundType", - "tags": [], - "label": "isEventPinned", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.isEventViewer", - "type": "CompoundType", - "tags": [], - "label": "isEventViewer", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.loadingEventIds", - "type": "Object", - "tags": [], - "label": "loadingEventIds", - "description": [], - "signature": [ - "readonly string[]" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.onEventDetailsPanelOpened", - "type": "Function", - "tags": [], - "label": "onEventDetailsPanelOpened", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.onRowSelected", - "type": "Function", - "tags": [], - "label": "onRowSelected", - "description": [], - "signature": [ - "({ eventIds, isSelected, }: { eventIds: string[]; isSelected: boolean; }) => void" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.onRowSelected.$1", - "type": "Object", - "tags": [], - "label": "__0", - "description": [], - "signature": [ - "{ eventIds: string[]; isSelected: boolean; }" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/store.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.onRuleChange", - "type": "Function", - "tags": [], - "label": "onRuleChange", - "description": [], - "signature": [ - "(() => void) | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.refetch", - "type": "Function", - "tags": [], - "label": "refetch", - "description": [], - "signature": [ - "(() => void) | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.rowIndex", - "type": "number", - "tags": [], - "label": "rowIndex", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.setEventsDeleted", - "type": "Function", - "tags": [], - "label": "setEventsDeleted", - "description": [], - "signature": [ - "(params: { eventIds: string[]; isDeleted: boolean; }) => void" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.setEventsDeleted.$1", - "type": "Object", - "tags": [], - "label": "params", - "description": [], - "signature": [ - "{ eventIds: string[]; isDeleted: boolean; }" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.setEventsLoading", - "type": "Function", - "tags": [], - "label": "setEventsLoading", - "description": [], - "signature": [ - "(params: { eventIds: string[]; isLoading: boolean; }) => void" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.setEventsLoading.$1", - "type": "Object", - "tags": [], - "label": "params", - "description": [], - "signature": [ - "{ eventIds: string[]; isLoading: boolean; }" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.showCheckboxes", - "type": "boolean", - "tags": [], - "label": "showCheckboxes", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.showNotes", - "type": "CompoundType", - "tags": [], - "label": "showNotes", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.tabType", - "type": "string", - "tags": [], - "label": "tabType", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.timelineId", - "type": "string", - "tags": [], - "label": "timelineId", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.toggleShowNotes", - "type": "Function", - "tags": [], - "label": "toggleShowNotes", - "description": [], - "signature": [ - "(() => void) | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-common.ActionProps.width", - "type": "number", - "tags": [], - "label": "width", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, + } + ], + "objects": [], + "start": { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart", + "type": "Interface", + "tags": [], + "label": "TimelinesUIStart", + "description": [], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart.getHoverActions", + "type": "Function", + "tags": [], + "label": "getHoverActions", + "description": [], + "signature": [ + "() => ", + "HoverActionsConfig" + ], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart.getTimelineReducer", + "type": "Function", + "tags": [], + "label": "getTimelineReducer", + "description": [], + "signature": [ + "() => any" + ], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart.getLoadingPanel", + "type": "Function", + "tags": [], + "label": "getLoadingPanel", + "description": [], + "signature": [ + "(props: ", + "LoadingPanelProps", + ") => React.ReactElement<", + "LoadingPanelProps", + ", string | React.JSXElementConstructor>" + ], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart.getLoadingPanel.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "LoadingPanelProps" + ], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart.getLastUpdated", + "type": "Function", + "tags": [], + "label": "getLastUpdated", + "description": [], + "signature": [ + "(props: ", + "LastUpdatedAtProps", + ") => React.ReactElement<", + "LastUpdatedAtProps", + ", string | React.JSXElementConstructor>" + ], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart.getLastUpdated.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "LastUpdatedAtProps" + ], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart.getUseAddToTimeline", + "type": "Function", + "tags": [], + "label": "getUseAddToTimeline", + "description": [], + "signature": [ + "() => (props: ", + "UseAddToTimelineProps", + ") => ", + "UseAddToTimeline" + ], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart.getUseAddToTimelineSensor", + "type": "Function", + "tags": [], + "label": "getUseAddToTimelineSensor", + "description": [], + "signature": [ + "() => (api: ", + "SensorAPI", + ") => void" + ], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart.setTimelineEmbeddedStore", + "type": "Function", + "tags": [], + "label": "setTimelineEmbeddedStore", + "description": [], + "signature": [ + "(store: ", + "Store", + ") => void" + ], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "timelines", + "id": "def-public.TimelinesUIStart.setTimelineEmbeddedStore.$1", + "type": "Object", + "tags": [], + "label": "store", + "description": [], + "signature": [ + "Store", + "" + ], + "path": "x-pack/plugins/timelines/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "timelines", + "id": "def-server.TimelinesPluginUI", + "type": "Interface", + "tags": [], + "label": "TimelinesPluginUI", + "description": [], + "path": "x-pack/plugins/timelines/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "timelines", + "id": "def-server.TimelinesPluginStart", + "type": "Interface", + "tags": [], + "label": "TimelinesPluginStart", + "description": [], + "path": "x-pack/plugins/timelines/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ { "parentPluginId": "timelines", "id": "def-common.BrowserField", @@ -4372,297 +1499,74 @@ "description": [], "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.BrowserField.esTypes", - "type": "Array", - "tags": [], - "label": "esTypes", - "description": [], - "signature": [ - "string[] | undefined" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.BrowserField.subType", - "type": "CompoundType", - "tags": [], - "label": "subType", - "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.IFieldSubType", - "text": "IFieldSubType" - }, - " | undefined" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.BrowserField.readFromDocValues", - "type": "boolean", - "tags": [], - "label": "readFromDocValues", - "description": [], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.BrowserField.runtimeField", - "type": "Object", - "tags": [], - "label": "runtimeField", - "description": [], - "signature": [ - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.RuntimeField", - "text": "RuntimeField" - }, - " | undefined" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer", - "type": "Interface", - "tags": [], - "label": "ColumnRenderer", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.isInstance", - "type": "Function", - "tags": [], - "label": "isInstance", - "description": [], - "signature": [ - "(columnName: string, data: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.TimelineNonEcsData", - "text": "TimelineNonEcsData" - }, - "[]) => boolean" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.isInstance.$1", - "type": "string", - "tags": [], - "label": "columnName", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.isInstance.$2", - "type": "Array", - "tags": [], - "label": "data", - "description": [], - "signature": [ - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.TimelineNonEcsData", - "text": "TimelineNonEcsData" - }, - "[]" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.renderColumn", - "type": "Function", - "tags": [], - "label": "renderColumn", - "description": [], - "signature": [ - "({ columnName, eventId, field, timelineId, truncate, values, linkValues, }: { columnName: string; eventId: string; field: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderOptions", - "text": "ColumnHeaderOptions" - }, - "; timelineId: string; truncate?: boolean | undefined; values: string[] | null | undefined; linkValues?: string[] | null | undefined; }) => React.ReactNode" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.renderColumn.$1", - "type": "Object", - "tags": [], - "label": "{\n columnName,\n eventId,\n field,\n timelineId,\n truncate,\n values,\n linkValues,\n }", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.renderColumn.$1.columnName", - "type": "string", - "tags": [], - "label": "columnName", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.renderColumn.$1.eventId", - "type": "string", - "tags": [], - "label": "eventId", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.renderColumn.$1.field", - "type": "CompoundType", - "tags": [], - "label": "field", - "description": [], - "signature": [ - "Pick<", - "EuiDataGridColumn", - ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", - "TGridCellAction", - "[] | undefined; category?: string | undefined; columnHeaderType: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderType", - "text": "ColumnHeaderType" - }, - "; description?: string | null | undefined; esTypes?: string[] | undefined; example?: string | number | null | undefined; format?: string | undefined; linkField?: string | undefined; placeholder?: string | undefined; subType?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.IFieldSubType", - "text": "IFieldSubType" - }, - " | undefined; type?: string | undefined; }" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.renderColumn.$1.timelineId", - "type": "string", - "tags": [], - "label": "timelineId", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.renderColumn.$1.truncate", - "type": "CompoundType", - "tags": [], - "label": "truncate", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.renderColumn.$1.values", - "type": "CompoundType", - "tags": [], - "label": "values", - "description": [], - "signature": [ - "string[] | null | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnRenderer.renderColumn.$1.linkValues", - "type": "CompoundType", - "tags": [], - "label": "linkValues", - "description": [], - "signature": [ - "string[] | null | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false - } - ] - } + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.BrowserField.esTypes", + "type": "Array", + "tags": [], + "label": "esTypes", + "description": [], + "signature": [ + "string[] | undefined" ], - "returnComment": [] + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.BrowserField.subType", + "type": "CompoundType", + "tags": [], + "label": "subType", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.IFieldSubType", + "text": "IFieldSubType" + }, + " | undefined" + ], + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.BrowserField.readFromDocValues", + "type": "boolean", + "tags": [], + "label": "readFromDocValues", + "description": [], + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.BrowserField.runtimeField", + "type": "Object", + "tags": [], + "label": "runtimeField", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.RuntimeField", + "text": "RuntimeField" + }, + " | undefined" + ], + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -4972,335 +1876,121 @@ }, { "parentPluginId": "timelines", - "id": "def-common.EqlOptionsSelected.query", - "type": "string", - "tags": [], - "label": "query", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.EqlOptionsSelected.size", - "type": "number", - "tags": [], - "label": "size", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.FieldInfo", - "type": "Interface", - "tags": [], - "label": "FieldInfo", - "description": [], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.FieldInfo.category", - "type": "string", - "tags": [], - "label": "category", - "description": [], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.FieldInfo.description", - "type": "string", - "tags": [], - "label": "description", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.FieldInfo.example", - "type": "CompoundType", - "tags": [], - "label": "example", - "description": [], - "signature": [ - "string | number | undefined" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.FieldInfo.format", - "type": "string", - "tags": [], - "label": "format", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.FieldInfo.name", - "type": "string", - "tags": [], - "label": "name", - "description": [], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.FieldInfo.type", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps", - "type": "Interface", - "tags": [], - "label": "HeaderActionProps", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.width", - "type": "number", - "tags": [], - "label": "width", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.browserFields", - "type": "Object", - "tags": [], - "label": "browserFields", - "description": [], - "signature": [ - "{ readonly [x: string]: Partial<", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.BrowserField", - "text": "BrowserField" - }, - ">; }" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.columnHeaders", - "type": "Array", - "tags": [], - "label": "columnHeaders", - "description": [], - "signature": [ - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderOptions", - "text": "ColumnHeaderOptions" - }, - "[]" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.fieldBrowserOptions", - "type": "Object", - "tags": [], - "label": "fieldBrowserOptions", - "description": [], - "signature": [ - { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.FieldBrowserOptions", - "text": "FieldBrowserOptions" - }, - " | undefined" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.isEventViewer", - "type": "CompoundType", + "id": "def-common.EqlOptionsSelected.query", + "type": "string", "tags": [], - "label": "isEventViewer", + "label": "query", "description": [], "signature": [ - "boolean | undefined" + "string | undefined" ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", + "path": "x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.isSelectAllChecked", - "type": "boolean", + "id": "def-common.EqlOptionsSelected.size", + "type": "number", "tags": [], - "label": "isSelectAllChecked", + "label": "size", "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", + "signature": [ + "number | undefined" + ], + "path": "x-pack/plugins/timelines/common/search_strategy/timeline/events/eql/index.ts", "deprecated": false, "trackAdoption": false - }, + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "timelines", + "id": "def-common.FieldInfo", + "type": "Interface", + "tags": [], + "label": "FieldInfo", + "description": [], + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.onSelectAll", - "type": "Function", + "id": "def-common.FieldInfo.category", + "type": "string", "tags": [], - "label": "onSelectAll", + "label": "category", "description": [], - "signature": [ - "({ isSelected }: { isSelected: boolean; }) => void" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.onSelectAll.$1", - "type": "Object", - "tags": [], - "label": "{ isSelected }", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.onSelectAll.$1.isSelected", - "type": "boolean", - "tags": [], - "label": "isSelected", - "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [] + "trackAdoption": false }, { "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.showEventsSelect", - "type": "boolean", + "id": "def-common.FieldInfo.description", + "type": "string", "tags": [], - "label": "showEventsSelect", + "label": "description", "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.showSelectAllCheckbox", - "type": "boolean", + "id": "def-common.FieldInfo.example", + "type": "CompoundType", "tags": [], - "label": "showSelectAllCheckbox", + "label": "example", "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", + "signature": [ + "string | number | undefined" + ], + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.sort", - "type": "Array", + "id": "def-common.FieldInfo.format", + "type": "string", "tags": [], - "label": "sort", + "label": "format", "description": [], "signature": [ - "SortColumnTable", - "[]" + "string | undefined" ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.tabType", + "id": "def-common.FieldInfo.name", "type": "string", "tags": [], - "label": "tabType", + "label": "name", "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "timelines", - "id": "def-common.HeaderActionProps.timelineId", + "id": "def-common.FieldInfo.type", "type": "string", "tags": [], - "label": "timelineId", + "label": "type", "description": [], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", "deprecated": false, "trackAdoption": false } @@ -6312,8 +3002,7 @@ "label": "filterStatus", "description": [], "signature": [ - "AlertStatus", - " | undefined" + "AlertWorkflowStatus | undefined" ], "path": "x-pack/plugins/timelines/common/search_strategy/timeline/events/all/index.ts", "deprecated": false, @@ -7211,21 +3900,6 @@ } ], "misc": [ - { - "parentPluginId": "timelines", - "id": "def-common.AlertWorkflowStatus", - "type": "Type", - "tags": [], - "label": "AlertWorkflowStatus", - "description": [], - "signature": [ - "\"open\" | \"closed\" | \"acknowledged\"" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "timelines", "id": "def-common.BeatFields", @@ -7344,16 +4018,10 @@ "signature": [ "Pick<", "EuiDataGridColumn", - ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; tGridCellActions?: ", - "TGridCellAction", + ", \"schema\" | \"id\" | \"actions\" | \"display\" | \"defaultSortDirection\" | \"displayAsText\" | \"initialWidth\" | \"isSortable\"> & { aggregatable?: boolean | undefined; dataTableCellActions?: ", + "DataTableCellAction", "[] | undefined; category?: string | undefined; columnHeaderType: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ColumnHeaderType", - "text": "ColumnHeaderType" - }, + "ColumnHeaderType", "; description?: string | null | undefined; esTypes?: string[] | undefined; example?: string | number | null | undefined; format?: string | undefined; linkField?: string | undefined; placeholder?: string | undefined; subType?: ", { "pluginId": "@kbn/es-query", @@ -7364,55 +4032,6 @@ }, " | undefined; type?: string | undefined; }" ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnHeaderType", - "type": "Type", - "tags": [], - "label": "ColumnHeaderType", - "description": [], - "signature": [ - "\"not-filtered\" | \"text-filter\"" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ColumnId", - "type": "Type", - "tags": [], - "label": "ColumnId", - "description": [ - "Uniquely identifies a column" - ], - "signature": [ - "string" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/columns/index.tsx", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.ControlColumnProps", - "type": "Type", - "tags": [], - "label": "ControlColumnProps", - "description": [], - "signature": [ - "Omit<", - "EuiDataGridControlColumn", - ", keyof AdditionalControlColumnProps> & Partial" - ], "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", "deprecated": false, "trackAdoption": false, @@ -7540,44 +4159,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "timelines", - "id": "def-common.GenericActionRowCellRenderProps", - "type": "Type", - "tags": [], - "label": "GenericActionRowCellRenderProps", - "description": [], - "signature": [ - "{ columnId: string; rowIndex: number; }" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.HeaderCellRender", - "type": "Type", - "tags": [], - "label": "HeaderCellRender", - "description": [], - "signature": [ - "React.ComponentType<{}> | React.ComponentType<", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.HeaderActionProps", - "text": "HeaderActionProps" - }, - ">" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "timelines", "id": "def-common.IndexFieldsStrategyRequest", @@ -7630,117 +4211,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "timelines", - "id": "def-common.RowCellRender", - "type": "Type", - "tags": [], - "label": "RowCellRender", - "description": [], - "signature": [ - "React.JSXElementConstructor<", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.GenericActionRowCellRenderProps", - "text": "GenericActionRowCellRenderProps" - }, - "> | ((props: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.GenericActionRowCellRenderProps", - "text": "GenericActionRowCellRenderProps" - }, - ") => JSX.Element) | React.JSXElementConstructor<", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ActionProps", - "text": "ActionProps" - }, - "> | ((props: ", - { - "pluginId": "timelines", - "scope": "common", - "docId": "kibTimelinesPluginApi", - "section": "def-common.ActionProps", - "text": "ActionProps" - }, - ") => JSX.Element)" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.SetEventsDeleted", - "type": "Type", - "tags": [], - "label": "SetEventsDeleted", - "description": [], - "signature": [ - "(params: { eventIds: string[]; isDeleted: boolean; }) => void" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.SetEventsDeleted.$1", - "type": "Object", - "tags": [], - "label": "params", - "description": [], - "signature": [ - "{ eventIds: string[]; isDeleted: boolean; }" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "timelines", - "id": "def-common.SetEventsLoading", - "type": "Type", - "tags": [], - "label": "SetEventsLoading", - "description": [], - "signature": [ - "(params: { eventIds: string[]; isLoading: boolean; }) => void" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "timelines", - "id": "def-common.SetEventsLoading.$1", - "type": "Object", - "tags": [], - "label": "params", - "description": [], - "signature": [ - "{ eventIds: string[]; isLoading: boolean; }" - ], - "path": "x-pack/plugins/timelines/common/types/timeline/actions/index.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "timelines", "id": "def-common.TimelineKpiStrategyRequest", diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 804656a3d20f1..482878aa675f5 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; @@ -21,16 +21,13 @@ Contact [Security solution](https://github.com/orgs/elastic/teams/security-solut | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 462 | 1 | 350 | 33 | +| 257 | 1 | 214 | 21 | ## Client ### Start -### Objects - - ### Functions diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 27fc618bceffe..6fbb110400c77 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-12-06 +date: 2022-12-08 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 401d2fbb4aaa7..489d1bc568121 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -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; \"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[]; }" + " & { tags?: string[] | undefined; kibana?: string[] | undefined; \"@timestamp\"?: string[] | undefined; \"event.action\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: 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, diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 69ddb5c1093bf..3b1d164b98bed 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 5fa67581c820d..807bf67b00b7b 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-12-06 +date: 2022-12-08 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 a80c3ff9fb9df..54999262e22a1 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 6ed9dc3725a5a..9c451a90c12f9 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 516370395fbb4..47d213c3a43d0 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-12-06 +date: 2022-12-08 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 541ddaa1d833a..583defb247b0e 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-12-06 +date: 2022-12-08 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 c712e9aedc5b1..78fa7ede18c62 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-12-06 +date: 2022-12-08 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 1b6f8246208a0..01cc5d9cf3658 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-12-06 +date: 2022-12-08 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 558cc3e447e90..e5088ee9dfdfb 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-12-06 +date: 2022-12-08 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 33f455584f77d..61f7dbce948c5 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-12-06 +date: 2022-12-08 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 ada62356443b5..ae59b3963bdbc 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-12-06 +date: 2022-12-08 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 a8daaef1592a8..02873519e4be4 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-12-06 +date: 2022-12-08 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 c2925dadbc203..43501f3d7edb7 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.devdocs.json b/api_docs/vis_type_pie.devdocs.json index dbc657689af05..18bc8c20b058c 100644 --- a/api_docs/vis_type_pie.devdocs.json +++ b/api_docs/vis_type_pie.devdocs.json @@ -241,21 +241,6 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false - }, - { - "parentPluginId": "visTypePie", - "id": "def-common.LEGACY_PIE_CHARTS_LIBRARY", - "type": "string", - "tags": [], - "label": "LEGACY_PIE_CHARTS_LIBRARY", - "description": [], - "signature": [ - "\"visualization:visualize:legacyPieChartsLibrary\"" - ], - "path": "src/plugins/vis_types/pie/common/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false } ], "objects": [] diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index b2cf964c6ceb6..16a97238a2c32 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualization | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 12 | 0 | 12 | 1 | +| 11 | 0 | 11 | 1 | ## Client diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index fa6224b4060bf..9add7c7a351a9 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-12-06 +date: 2022-12-08 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 1b25eee004e2b..dd785a58913b1 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-12-06 +date: 2022-12-08 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 b10f3bb9f6da9..07a7467cd46e4 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-12-06 +date: 2022-12-08 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 430151dc5c26f..cf7c0266026fd 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.devdocs.json b/api_docs/vis_type_vislib.devdocs.json index 2ba5f096eecd3..780de832a2be4 100644 --- a/api_docs/vis_type_vislib.devdocs.json +++ b/api_docs/vis_type_vislib.devdocs.json @@ -40,7 +40,7 @@ "label": "type", "description": [], "signature": [ - "\"metric\" | \"goal\" | \"gauge\" | \"heatmap\" | \"pie\"" + "\"metric\" | \"goal\" | \"gauge\" | \"heatmap\"" ], "path": "src/plugins/vis_types/vislib/public/types.ts", "deprecated": false, @@ -359,7 +359,7 @@ "label": "VislibChartType", "description": [], "signature": [ - "\"metric\" | \"goal\" | \"gauge\" | \"heatmap\" | \"pie\"" + "\"metric\" | \"goal\" | \"gauge\" | \"heatmap\"" ], "path": "src/plugins/vis_types/vislib/public/types.ts", "deprecated": false, @@ -408,7 +408,7 @@ "label": "VislibChartType", "description": [], "signature": [ - "{ readonly Pie: \"pie\"; readonly Heatmap: \"heatmap\"; readonly Gauge: \"gauge\"; readonly Goal: \"goal\"; readonly Metric: \"metric\"; }" + "{ readonly Heatmap: \"heatmap\"; readonly Gauge: \"gauge\"; readonly Goal: \"goal\"; readonly Metric: \"metric\"; }" ], "path": "src/plugins/vis_types/vislib/public/types.ts", "deprecated": false, diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index cdf56efe992a0..0603b97022083 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,12 +8,12 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2022-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; -Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts. +Contains the vislib visualizations. These are the classical area/line/bar, gauge/goal and heatmap charts. We want to replace them with elastic-charts. Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-visualizations) for questions regarding this plugin. diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 2f6c080e9a626..ac1811b9d272b 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-12-06 +date: 2022-12-08 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 879e5181b4c14..ac4d22d9f16d8 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-12-06 +date: 2022-12-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/docs/apm/apm-spaces.asciidoc b/docs/apm/apm-spaces.asciidoc index c43a512768fad..093b7560cd5aa 100644 --- a/docs/apm/apm-spaces.asciidoc +++ b/docs/apm/apm-spaces.asciidoc @@ -56,7 +56,7 @@ aliases for each service environment: [options="header"] |==== -| Index setting | `production` env | `staging` evn +| Index setting | `production` env | `staging` env | Error | `production-logs-apm` | `staging-logs-apm` | Span/Transaction | `production-traces-apm` | `staging-traces-apm` | Metrics | `production-metrics-apm` | `staging-metrics-apm` diff --git a/docs/index-custom-title-page.html b/docs/index-custom-title-page.html index 4c9fe7af5ba59..7af50716913b4 100644 --- a/docs/index-custom-title-page.html +++ b/docs/index-custom-title-page.html @@ -63,8 +63,8 @@

Bring your data to life

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

diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index af036a6e9b432..f5400d7ee286a 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -540,10 +540,6 @@ Enables the legacy time axis for charts in Lens, Discover, Visualize and TSVB [[visualization-heatmap-maxbuckets]]`visualization:heatmap:maxBuckets`:: The maximum number of buckets a datasource can return. High numbers can have a negative impact on your browser rendering performance. -[[visualization-visualize-pieChartslibrary]]`visualization:visualize:legacyPieChartsLibrary`:: -**The legacy pie charts are deprecated and will not be supported in a future version.** -The visualize editor uses new pie charts with improved performance, color palettes, label positioning, and more. Enable this option if you prefer to use the legacy charts library. - [[visualization-visualize-heatmapChartslibrary]]`visualization:visualize:legacyHeatmapChartsLibrary`:: Disable this option if you prefer to use the new heatmap charts with improved performance, legend settings, and more.. diff --git a/fleet_packages.json b/fleet_packages.json index 6a8508f04ca22..30d4b7f73824d 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -43,4 +43,4 @@ "name": "security_detection_engine", "version": "8.4.1" } -] +] \ No newline at end of file diff --git a/package.json b/package.json index 2f5d7a6349f14..d72dd572f09ad 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ }, "dependencies": { "@appland/sql-parser": "^1.5.1", - "@babel/runtime": "^7.20.1", + "@babel/runtime": "^7.20.6", "@dnd-kit/core": "^3.1.1", "@dnd-kit/sortable": "^4.0.0", "@dnd-kit/utilities": "^2.0.0", @@ -614,7 +614,7 @@ "react-fast-compare": "^2.0.4", "react-focus-on": "^3.7.0", "react-grid-layout": "^1.3.4", - "react-hook-form": "^7.39.7", + "react-hook-form": "^7.40.0", "react-intl": "^2.8.0", "react-is": "^17.0.2", "react-markdown": "^6.0.3", @@ -694,12 +694,12 @@ "devDependencies": { "@apidevtools/swagger-parser": "^10.0.3", "@babel/cli": "^7.19.3", - "@babel/core": "^7.20.2", + "@babel/core": "^7.20.5", "@babel/eslint-parser": "^7.19.1", "@babel/eslint-plugin": "^7.19.1", - "@babel/generator": "^7.20.4", + "@babel/generator": "^7.20.5", "@babel/helper-plugin-utils": "^7.20.2", - "@babel/parser": "^7.20.3", + "@babel/parser": "^7.20.5", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", @@ -711,8 +711,8 @@ "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@babel/register": "^7.18.9", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.2", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5", "@bazel/ibazel": "^0.16.2", "@bazel/typescript": "4.6.2", "@cypress/code-coverage": "^3.10.0", @@ -981,7 +981,7 @@ "argsplit": "^1.0.5", "autoprefixer": "^10.4.7", "axe-core": "^4.0.2", - "babel-jest": "^29.2.2", + "babel-jest": "^29.3.1", "babel-loader": "^8.2.5", "babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-istanbul": "^6.1.1", @@ -1088,7 +1088,7 @@ "ms-chromium-edge-driver": "^0.5.1", "mutation-observer": "^1.0.3", "nock": "12.0.3", - "node-sass": "^7.0.3", + "node-sass": "^8.0.0", "null-loader": "^3.0.0", "nyc": "^15.1.0", "oboe": "^2.1.4", @@ -1113,9 +1113,9 @@ "regenerate": "^1.4.0", "resolve": "^1.22.0", "rxjs-marbles": "^7.0.1", - "sass-loader": "^10.3.1", + "sass-loader": "^10.4.1", "selenium-webdriver": "^4.6.1", - "simple-git": "^3.10.0", + "simple-git": "^3.15.1", "sinon": "^7.4.2", "sort-package-json": "^1.53.1", "source-map": "^0.7.3", diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_setup.mock.ts b/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_setup.mock.ts index 0c4e25e846cab..5f922fef1502b 100644 --- a/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_setup.mock.ts +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_setup.mock.ts @@ -45,6 +45,7 @@ export function createCoreSetupMock({ const uiSettingsMock = { register: uiSettingsServiceMock.createSetupContract().register, + registerGlobal: uiSettingsServiceMock.createSetupContract().registerGlobal, }; const mock: CoreSetupMockType = { diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts index 363fca6430dbe..49b5a4b71da53 100644 --- a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts @@ -8,7 +8,7 @@ import { Agent as HttpAgent } from 'http'; import { Agent as HttpsAgent } from 'https'; -import { sampleEsClientMetrics } from '@kbn/core-metrics-server-mocks'; +import type { ElasticsearchClientsMetrics } from '@kbn/core-metrics-server'; import { createAgentStoreMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { getAgentsSocketsStatsMock } from './get_agents_sockets_stats.test.mocks'; import { ElasticsearchClientsMetricsCollector } from './elasticsearch_client'; @@ -16,6 +16,12 @@ import { getAgentsSocketsStats } from './get_agents_sockets_stats'; jest.mock('@kbn/core-elasticsearch-client-server-internal'); +export const sampleEsClientMetrics: ElasticsearchClientsMetrics = { + totalActiveSockets: 25, + totalIdleSockets: 2, + totalQueuedRequests: 0, +}; + describe('ElasticsearchClientsMetricsCollector', () => { test('#collect calls getAgentsSocketsStats with the Agents managed by the provided AgentManager', async () => { const agents = new Set([new HttpAgent(), new HttpsAgent()]); diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts index b937508094a84..9cd028c8c298c 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts @@ -250,6 +250,7 @@ export function createPluginSetupContext( }, uiSettings: { register: deps.uiSettings.register, + registerGlobal: deps.uiSettings.registerGlobal, }, getStartServices: () => plugin.startDependencies, deprecations: deps.deprecations.getRegistry(plugin.name), @@ -311,6 +312,7 @@ export function createPluginStartContext( }, uiSettings: { asScopedToClient: deps.uiSettings.asScopedToClient, + globalAsScopedToClient: deps.uiSettings.globalAsScopedToClient, }, coreUsageData: deps.coreUsageData, }; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.test.ts index 01bb3174cc56b..551c9dc1187eb 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.test.ts @@ -6,16 +6,22 @@ * Side Public License, v 1. */ -import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { getIndexForType } from './get_index_for_type'; +const createTypeRegistry = () => { + return { + getIndex: jest.fn(), + } as unknown as jest.Mocked; +}; + describe('getIndexForType', () => { const kibanaVersion = '8.0.0'; const defaultIndex = '.kibana'; - let typeRegistry: ReturnType; + let typeRegistry: ReturnType; beforeEach(() => { - typeRegistry = typeRegistryMock.create(); + typeRegistry = createTypeRegistry(); }); it('returns the correct index for a type specifying a custom index', () => { diff --git a/packages/core/ui-settings/core-ui-settings-browser-internal/src/__snapshots__/ui_settings_api.test.ts.snap b/packages/core/ui-settings/core-ui-settings-browser-internal/src/__snapshots__/ui_settings_api.test.ts.snap index b737c04a5f269..6599746d33bf1 100644 --- a/packages/core/ui-settings/core-ui-settings-browser-internal/src/__snapshots__/ui_settings_api.test.ts.snap +++ b/packages/core/ui-settings/core-ui-settings-browser-internal/src/__snapshots__/ui_settings_api.test.ts.snap @@ -119,3 +119,123 @@ Array [ ], ] `; + +exports[`#batchSetGlobal Buffers are always clear of previously buffered changes: two requests, second only sends bar, not foo 1`] = ` +Array [ + Array [ + "/foo/bar/api/kibana/global_settings", + Object { + "headers": Object { + "accept": "application/json", + "content-type": "application/json", + "kbn-version": "kibanaVersion", + }, + "method": "POST", + }, + ], + Array [ + "/foo/bar/api/kibana/global_settings", + Object { + "headers": Object { + "accept": "application/json", + "content-type": "application/json", + "kbn-version": "kibanaVersion", + }, + "method": "POST", + }, + ], +] +`; + +exports[`#batchSetGlobal Overwrites previously buffered values with new values for the same key: two requests, foo=d in final 1`] = ` +Array [ + Array [ + "/foo/bar/api/kibana/global_settings", + Object { + "headers": Object { + "accept": "application/json", + "content-type": "application/json", + "kbn-version": "kibanaVersion", + }, + "method": "POST", + }, + ], + Array [ + "/foo/bar/api/kibana/global_settings", + Object { + "headers": Object { + "accept": "application/json", + "content-type": "application/json", + "kbn-version": "kibanaVersion", + }, + "method": "POST", + }, + ], +] +`; + +exports[`#batchSetGlobal buffers changes while first request is in progress, sends buffered changes after first request completes: final, includes both requests 1`] = ` +Array [ + Array [ + "/foo/bar/api/kibana/global_settings", + Object { + "headers": Object { + "accept": "application/json", + "content-type": "application/json", + "kbn-version": "kibanaVersion", + }, + "method": "POST", + }, + ], + Array [ + "/foo/bar/api/kibana/global_settings", + Object { + "headers": Object { + "accept": "application/json", + "content-type": "application/json", + "kbn-version": "kibanaVersion", + }, + "method": "POST", + }, + ], +] +`; + +exports[`#batchSetGlobal rejects all promises for batched requests that fail: promise rejections 1`] = ` +Array [ + Object { + "error": [Error: invalid], + "isRejected": true, + }, + Object { + "error": [Error: invalid], + "isRejected": true, + }, + Object { + "error": [Error: invalid], + "isRejected": true, + }, +] +`; + +exports[`#batchSetGlobal rejects on 301 1`] = `"Moved Permanently"`; + +exports[`#batchSetGlobal rejects on 404 response 1`] = `"Request failed with status code: 404"`; + +exports[`#batchSetGlobal rejects on 500 1`] = `"Request failed with status code: 500"`; + +exports[`#batchSetGlobal sends a single change immediately: single change 1`] = ` +Array [ + Array [ + "/foo/bar/api/kibana/global_settings", + Object { + "headers": Object { + "accept": "application/json", + "content-type": "application/json", + "kbn-version": "kibanaVersion", + }, + "method": "POST", + }, + ], +] +`; diff --git a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_api.test.ts b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_api.test.ts index c250b78d68ce5..9fbbb4045583a 100644 --- a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_api.test.ts +++ b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_api.test.ts @@ -162,6 +162,123 @@ describe('#batchSet', () => { }); }); +describe('#batchSetGlobal', () => { + it('sends a single change immediately', async () => { + fetchMock.mock('*', { + body: { settings: {} }, + }); + + const { uiSettingsApi } = setup(); + await uiSettingsApi.batchSetGlobal('foo', 'bar'); + expect(fetchMock.calls()).toMatchSnapshot('single change'); + }); + + it('buffers changes while first request is in progress, sends buffered changes after first request completes', async () => { + fetchMock.mock('*', { + body: { settings: {} }, + }); + + const { uiSettingsApi } = setup(); + + uiSettingsApi.batchSetGlobal('foo', 'bar'); + const finalPromise = uiSettingsApi.batchSet('box', 'bar'); + + expect(uiSettingsApi.hasPendingChanges()).toBe(true); + await finalPromise; + expect(fetchMock.calls()).toMatchSnapshot('final, includes both requests'); + }); + + it('Overwrites previously buffered values with new values for the same key', async () => { + fetchMock.mock('*', { + body: { settings: {} }, + }); + + const { uiSettingsApi } = setup(); + + uiSettingsApi.batchSetGlobal('foo', 'a'); + uiSettingsApi.batchSetGlobal('foo', 'b'); + uiSettingsApi.batchSetGlobal('foo', 'c'); + await uiSettingsApi.batchSetGlobal('foo', 'd'); + + expect(fetchMock.calls()).toMatchSnapshot('two requests, foo=d in final'); + }); + + it('Buffers are always clear of previously buffered changes', async () => { + fetchMock.mock('*', { + body: { settings: {} }, + }); + + const { uiSettingsApi } = setup(); + uiSettingsApi.batchSetGlobal('foo', 'bar'); + uiSettingsApi.batchSetGlobal('bar', 'foo'); + await uiSettingsApi.batchSetGlobal('bar', 'box'); + + expect(fetchMock.calls()).toMatchSnapshot('two requests, second only sends bar, not foo'); + }); + + it('rejects on 404 response', async () => { + fetchMock.mock('*', { + status: 404, + body: 'not found', + }); + + const { uiSettingsApi } = setup(); + await expect(uiSettingsApi.batchSetGlobal('foo', 'bar')).rejects.toThrowErrorMatchingSnapshot(); + }); + + it('rejects on 301', async () => { + fetchMock.mock('*', { + status: 301, + body: 'redirect', + }); + + const { uiSettingsApi } = setup(); + await expect(uiSettingsApi.batchSetGlobal('foo', 'bar')).rejects.toThrowErrorMatchingSnapshot(); + }); + + it('rejects on 500', async () => { + fetchMock.mock('*', { + status: 500, + body: 'redirect', + }); + + const { uiSettingsApi } = setup(); + await expect(uiSettingsApi.batchSetGlobal('foo', 'bar')).rejects.toThrowErrorMatchingSnapshot(); + }); + + it('rejects all promises for batched requests that fail', async () => { + fetchMock.once('*', { + body: { settings: {} }, + }); + fetchMock.once( + '*', + { + status: 400, + body: { message: 'invalid' }, + }, + { + overwriteRoutes: false, + } + ); + + const { uiSettingsApi } = setup(); + // trigger the initial sync request, which enabled buffering + uiSettingsApi.batchSetGlobal('foo', 'bar'); + + // buffer some requests so they will be sent together + await expect( + Promise.all([ + settlePromise(uiSettingsApi.batchSetGlobal('foo', 'a')), + settlePromise(uiSettingsApi.batchSetGlobal('bar', 'b')), + settlePromise(uiSettingsApi.batchSetGlobal('baz', 'c')), + ]) + ).resolves.toMatchSnapshot('promise rejections'); + + // ensure only two requests were sent + expect(fetchMock.calls()).toHaveLength(2); + }); +}); + describe('#getLoadingCount$()', () => { it('emits the current number of active requests', async () => { fetchMock.once('*', { diff --git a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_api.ts b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_api.ts index 2686307357723..98141eb1163b5 100644 --- a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_api.ts +++ b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_api.ts @@ -10,6 +10,7 @@ import { BehaviorSubject } from 'rxjs'; import type { HttpSetup } from '@kbn/core-http-browser'; import type { UiSettingsState } from '@kbn/core-ui-settings-browser'; +import { UiSettingsScope } from '@kbn/core-ui-settings-common'; export interface UiSettingsApiResponse { settings: UiSettingsState; @@ -64,7 +65,32 @@ export class UiSettingsApi { }, }; - this.flushPendingChanges(); + this.flushPendingChanges('namespace'); + }); + } + + public batchSetGlobal(key: string, value: any) { + return new Promise((resolve, reject) => { + const prev = this.pendingChanges || NOOP_CHANGES; + + this.pendingChanges = { + values: { + ...prev.values, + [key]: value, + }, + + callback(error, resp) { + prev.callback(error, resp); + + if (error) { + reject(error); + } else { + resolve(resp!); + } + }, + }; + + this.flushPendingChanges('global'); }); } @@ -97,7 +123,7 @@ export class UiSettingsApi { * progress) then another request will be started until all pending changes have been * sent to the server. */ - private async flushPendingChanges() { + private async flushPendingChanges(scope: UiSettingsScope) { if (!this.pendingChanges) { return; } @@ -111,10 +137,10 @@ export class UiSettingsApi { try { this.sendInProgress = true; - + const path = scope === 'namespace' ? '/api/kibana/settings' : '/api/kibana/global_settings'; changes.callback( undefined, - await this.sendRequest('POST', '/api/kibana/settings', { + await this.sendRequest('POST', path, { changes: changes.values, }) ); @@ -122,7 +148,7 @@ export class UiSettingsApi { changes.callback(error); } finally { this.sendInProgress = false; - this.flushPendingChanges(); + this.flushPendingChanges(scope); } } diff --git a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client.test.ts b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client.test.ts index f8c5dbfc347dd..8e8a4af6aa00c 100644 --- a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client.test.ts +++ b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client.test.ts @@ -19,17 +19,21 @@ function setup(options: { defaults?: any; initialSettings?: any } = {}) { const batchSet = jest.fn(() => ({ settings: {}, })); + const batchSetGlobal = jest.fn(() => ({ + settings: {}, + })); done$ = new Subject(); const client = new UiSettingsClient({ defaults, initialSettings, api: { batchSet, + batchSetGlobal, } as any, done$, }); - return { client, batchSet }; + return { client, batchSet, batchSetGlobal }; } afterEach(() => { diff --git a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client.ts b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client.ts index d5b6b7f1513b2..52e599cef6298 100644 --- a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client.ts +++ b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client.ts @@ -6,131 +6,15 @@ * Side Public License, v 1. */ -import { cloneDeep, defaultsDeep } from 'lodash'; -import { Observable, Subject, concat, defer, of } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; - -import { UserProvidedValues, PublicUiSettingsParams } from '@kbn/core-ui-settings-common'; -import { IUiSettingsClient, UiSettingsState } from '@kbn/core-ui-settings-browser'; - -import { UiSettingsApi } from './ui_settings_api'; - -interface UiSettingsClientParams { - api: UiSettingsApi; - defaults: Record; - initialSettings?: UiSettingsState; - done$: Observable; -} - -export class UiSettingsClient implements IUiSettingsClient { - private readonly update$ = new Subject<{ key: string; newValue: any; oldValue: any }>(); - private readonly updateErrors$ = new Subject(); - - private readonly api: UiSettingsApi; - private readonly defaults: Record; - private cache: Record; +import { defaultsDeep } from 'lodash'; +import { UiSettingsClientCommon, UiSettingsClientParams } from './ui_settings_client_common'; +export class UiSettingsClient extends UiSettingsClientCommon { constructor(params: UiSettingsClientParams) { - this.api = params.api; - this.defaults = cloneDeep(params.defaults); - this.cache = defaultsDeep({}, this.defaults, cloneDeep(params.initialSettings)); - - params.done$.subscribe({ - complete: () => { - this.update$.complete(); - this.updateErrors$.complete(); - }, - }); - } - - getAll() { - return cloneDeep(this.cache); - } - - get(key: string, defaultOverride?: T) { - const declared = this.isDeclared(key); - - if (!declared && defaultOverride !== undefined) { - return defaultOverride; - } - - if (!declared) { - throw new Error( - `Unexpected \`IUiSettingsClient.get("${key}")\` call on unrecognized configuration setting "${key}". -Setting an initial value via \`IUiSettingsClient.set("${key}", value)\` before attempting to retrieve -any custom setting value for "${key}" may fix this issue. -You can use \`IUiSettingsClient.get("${key}", defaultValue)\`, which will just return -\`defaultValue\` when the key is unrecognized.` - ); - } - - const type = this.cache[key].type; - const userValue = this.cache[key].userValue; - const defaultValue = defaultOverride !== undefined ? defaultOverride : this.cache[key].value; - const value = userValue == null ? defaultValue : userValue; - - if (type === 'json') { - return JSON.parse(value); - } - - if (type === 'number') { - return parseFloat(value); - } - - return value; - } - - get$(key: string, defaultOverride?: T) { - return concat( - defer(() => of(this.get(key, defaultOverride))), - this.update$.pipe( - filter((update) => update.key === key), - map(() => this.get(key, defaultOverride)) - ) - ); - } - - async set(key: string, value: any) { - return await this.update(key, value); + super(params); } - async remove(key: string) { - return await this.update(key, null); - } - - isDeclared(key: string) { - return key in this.cache; - } - - isDefault(key: string) { - return !this.isDeclared(key) || this.cache[key].userValue == null; - } - - isCustom(key: string) { - return this.isDeclared(key) && !('value' in this.cache[key]); - } - - isOverridden(key: string) { - return this.isDeclared(key) && Boolean(this.cache[key].isOverridden); - } - - getUpdate$() { - return this.update$.asObservable(); - } - - getUpdateErrors$() { - return this.updateErrors$.asObservable(); - } - - private assertUpdateAllowed(key: string) { - if (this.isOverridden(key)) { - throw new Error( - `Unable to update "${key}" because its value is overridden by the Kibana server` - ); - } - } - - private async update(key: string, newVal: any): Promise { + async update(key: string, newVal: any): Promise { this.assertUpdateAllowed(key); const declared = this.isDeclared(key); @@ -156,27 +40,4 @@ You can use \`IUiSettingsClient.get("${key}", defaultValue)\`, which will just r return false; } } - - private setLocally(key: string, newValue: any) { - this.assertUpdateAllowed(key); - - if (!this.isDeclared(key)) { - this.cache[key] = {}; - } - - const oldValue = this.get(key); - - if (newValue === null) { - delete this.cache[key].userValue; - } else { - const { type } = this.cache[key]; - if (type === 'json' && typeof newValue !== 'string') { - this.cache[key].userValue = JSON.stringify(newValue); - } else { - this.cache[key].userValue = newValue; - } - } - - this.update$.next({ key, newValue, oldValue }); - } } diff --git a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client_common.ts b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client_common.ts new file mode 100644 index 0000000000000..17630ab5dda8d --- /dev/null +++ b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_client_common.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { cloneDeep, defaultsDeep } from 'lodash'; +import { Observable, Subject, concat, defer, of } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; + +import { UserProvidedValues, PublicUiSettingsParams } from '@kbn/core-ui-settings-common'; +import { IUiSettingsClient, UiSettingsState } from '@kbn/core-ui-settings-browser'; + +import { UiSettingsApi } from './ui_settings_api'; + +export interface UiSettingsClientParams { + api: UiSettingsApi; + defaults: Record; + initialSettings?: UiSettingsState; + done$: Observable; +} + +export abstract class UiSettingsClientCommon implements IUiSettingsClient { + protected readonly update$ = new Subject<{ key: string; newValue: any; oldValue: any }>(); + protected readonly updateErrors$ = new Subject(); + + protected readonly api: UiSettingsApi; + protected readonly defaults: Record; + protected cache: Record; + + constructor(params: UiSettingsClientParams) { + this.api = params.api; + this.defaults = cloneDeep(params.defaults); + this.cache = defaultsDeep({}, this.defaults, cloneDeep(params.initialSettings)); + + params.done$.subscribe({ + complete: () => { + this.update$.complete(); + this.updateErrors$.complete(); + }, + }); + } + + getAll() { + return cloneDeep(this.cache); + } + + get(key: string, defaultOverride?: T) { + const declared = this.isDeclared(key); + if (!declared && defaultOverride !== undefined) { + return defaultOverride; + } + + if (!declared) { + throw new Error( + `Unexpected \`IUiSettingsClient.get("${key}")\` call on unrecognized configuration setting "${key}". +Setting an initial value via \`IUiSettingsClient.set("${key}", value)\` before attempting to retrieve +any custom setting value for "${key}" may fix this issue. +You can use \`IUiSettingsClient.get("${key}", defaultValue)\`, which will just return +\`defaultValue\` when the key is unrecognized.` + ); + } + + const type = this.cache[key].type; + const userValue = this.cache[key].userValue; + const defaultValue = defaultOverride !== undefined ? defaultOverride : this.cache[key].value; + const value = userValue == null ? defaultValue : userValue; + if (type === 'json') { + return JSON.parse(value); + } + + if (type === 'number') { + return parseFloat(value); + } + + return value; + } + + get$(key: string, defaultOverride?: T) { + return concat( + defer(() => of(this.get(key, defaultOverride))), + this.update$.pipe( + filter((update) => update.key === key), + map(() => this.get(key, defaultOverride)) + ) + ); + } + + async set(key: string, value: any) { + return await this.update(key, value); + } + + async remove(key: string) { + return await this.update(key, null); + } + + isDeclared(key: string) { + return key in this.cache; + } + + isDefault(key: string) { + return !this.isDeclared(key) || this.cache[key].userValue == null; + } + + isCustom(key: string) { + return this.isDeclared(key) && !('value' in this.cache[key]); + } + + isOverridden(key: string) { + return this.isDeclared(key) && Boolean(this.cache[key].isOverridden); + } + + getUpdate$() { + return this.update$.asObservable(); + } + + getUpdateErrors$() { + return this.updateErrors$.asObservable(); + } + + protected assertUpdateAllowed(key: string) { + if (this.isOverridden(key)) { + throw new Error( + `Unable to update "${key}" because its value is overridden by the Kibana server` + ); + } + } + + protected abstract update(key: string, newVal: any): Promise; + + protected setLocally(key: string, newValue: any) { + this.assertUpdateAllowed(key); + + if (!this.isDeclared(key)) { + this.cache[key] = {}; + } + + const oldValue = this.get(key); + + if (newValue === null) { + delete this.cache[key].userValue; + } else { + const { type } = this.cache[key]; + if (type === 'json' && typeof newValue !== 'string') { + this.cache[key].userValue = JSON.stringify(newValue); + } else { + this.cache[key].userValue = newValue; + } + } + + this.update$.next({ key, newValue, oldValue }); + } +} diff --git a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_global_client.test.ts b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_global_client.test.ts new file mode 100644 index 0000000000000..d1931ef916058 --- /dev/null +++ b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_global_client.test.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Subject } from 'rxjs'; +import { take, toArray } from 'rxjs/operators'; + +import { UiSettingsGlobalClient } from './ui_settings_global_client'; + +let done$: Subject; + +function setup(options: { defaults?: any; initialSettings?: any } = {}) { + const { defaults = { dateFormat: { value: 'Browser' } }, initialSettings = {} } = options; + + const batchSetGlobal = jest.fn(() => ({ + settings: {}, + })); + done$ = new Subject(); + const client = new UiSettingsGlobalClient({ + defaults, + initialSettings, + api: { + batchSetGlobal, + } as any, + done$, + }); + + return { client, batchSetGlobal }; +} + +afterEach(() => { + done$.complete(); +}); + +describe('#get$', () => { + it('emits the default override if no value is set, or if the value is removed', async () => { + const { client } = setup(); + + setTimeout(() => { + client.set('dateFormat', 'new format'); + }, 10); + + setTimeout(() => { + client.remove('dateFormat'); + }, 20); + + const values = await client + .get$('dateFormat', 'my default') + .pipe(take(3), toArray()) + .toPromise(); + + expect(values).toEqual(['my default', 'new format', 'my default']); + }); +}); + +describe('#set', () => { + it('resolves to false on failure', async () => { + const { client, batchSetGlobal } = setup(); + + batchSetGlobal.mockImplementation(() => { + throw new Error('Error in request'); + }); + + await expect(client.set('foo', 'bar')).resolves.toBe(false); + }); +}); + +describe('#remove', () => { + it('resolves to false on failure', async () => { + const { client, batchSetGlobal } = setup(); + + batchSetGlobal.mockImplementation(() => { + throw new Error('Error in request'); + }); + + await expect(client.remove('dateFormat')).resolves.toBe(false); + }); +}); diff --git a/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_global_client.ts b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_global_client.ts new file mode 100644 index 0000000000000..9ca11cf950022 --- /dev/null +++ b/packages/core/ui-settings/core-ui-settings-browser-internal/src/ui_settings_global_client.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { defaultsDeep } from 'lodash'; +import { UiSettingsClientCommon, UiSettingsClientParams } from './ui_settings_client_common'; + +export class UiSettingsGlobalClient extends UiSettingsClientCommon { + constructor(params: UiSettingsClientParams) { + super(params); + } + + async update(key: string, newVal: any): Promise { + this.assertUpdateAllowed(key); + + const declared = this.isDeclared(key); + const defaults = this.defaults; + + const oldVal = declared ? this.cache[key].userValue : undefined; + + const unchanged = oldVal === newVal; + if (unchanged) { + return true; + } + + const initialVal = declared ? this.get(key) : undefined; + this.setLocally(key, newVal); + + try { + const { settings } = await this.api.batchSetGlobal(key, newVal); + this.cache = defaultsDeep({}, defaults, settings); + return true; + } catch (error) { + this.setLocally(key, initialVal); + this.updateErrors$.next(error); + return false; + } + } +} diff --git a/packages/core/ui-settings/core-ui-settings-common/index.ts b/packages/core/ui-settings/core-ui-settings-common/index.ts index 19bada43b0521..26a1450d3a28a 100644 --- a/packages/core/ui-settings/core-ui-settings-common/index.ts +++ b/packages/core/ui-settings/core-ui-settings-common/index.ts @@ -12,4 +12,5 @@ export type { UiSettingsParams, PublicUiSettingsParams, UserProvidedValues, + UiSettingsScope, } from './src/ui_settings'; diff --git a/packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts b/packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts index ec0d900773c56..a9de76efd907a 100644 --- a/packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts +++ b/packages/core/ui-settings/core-ui-settings-common/src/ui_settings.ts @@ -87,6 +87,11 @@ export interface UiSettingsParams { type: UiCounterMetricType; name: string; }; + /** + * Scope of the setting. `Global` denotes a setting globally available across namespaces. `Namespace` denotes a setting + * scoped to a namespace. The default value is 'namespace' + */ + scope?: UiSettingsScope; } /** @@ -103,3 +108,8 @@ export interface UserProvidedValues { userValue?: T; isOverridden?: boolean; } + +/** + * Denotes the scope of the setting + */ +export type UiSettingsScope = 'namespace' | 'global'; diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/index.ts b/packages/core/ui-settings/core-ui-settings-server-internal/index.ts index 9fd6540587ab2..3abfd91c1273c 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/index.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/index.ts @@ -10,6 +10,7 @@ export { uiSettingsConfig, UiSettingsClient, UiSettingsService, + UiSettingsGlobalClient, CoreUiSettingsRouteHandlerContext, } from './src'; export type { diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/base_ui_settings_client.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/base_ui_settings_client.ts similarity index 100% rename from packages/core/ui-settings/core-ui-settings-server-internal/src/base_ui_settings_client.ts rename to packages/core/ui-settings/core-ui-settings-server-internal/src/clients/base_ui_settings_client.ts diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/index.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/index.ts new file mode 100644 index 0000000000000..ce3a22f385b28 --- /dev/null +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/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 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 { UiSettingsClient } from './ui_settings_client'; +export { UiSettingsGlobalClient } from './ui_settings_global_client'; +export { UiSettingsClientFactory } from './ui_settings_client_factory'; +export { UiSettingsDefaultsClient } from './ui_settings_defaults_client'; +export { BaseUiSettingsClient } from './base_ui_settings_client'; diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.mock.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.test.mock.ts similarity index 79% rename from packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.mock.ts rename to packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.test.mock.ts index d2a96352b36ea..8ebf915bd21c5 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.mock.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.test.mock.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import type { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; +import type { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config'; export const mockCreateOrUpgradeSavedConfig = jest.fn() as jest.MockedFunction< typeof createOrUpgradeSavedConfig >; -jest.mock('./create_or_upgrade_saved_config', () => ({ +jest.mock('../create_or_upgrade_saved_config', () => ({ createOrUpgradeSavedConfig: mockCreateOrUpgradeSavedConfig, })); diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.test.ts similarity index 99% rename from packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts rename to packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.test.ts index 7b7f83a82a6f9..2612c8778898b 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.test.ts @@ -8,14 +8,12 @@ import Chance from 'chance'; import { schema } from '@kbn/config-schema'; - import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { mockCreateOrUpgradeSavedConfig } from './ui_settings_client.test.mock'; - import { SavedObjectsClient } from '@kbn/core-saved-objects-api-server-internal'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { UiSettingsClient } from './ui_settings_client'; -import { CannotOverrideError } from './ui_settings_errors'; +import { CannotOverrideError } from '../ui_settings_errors'; const logger = loggingSystemMock.create().get(); diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.ts new file mode 100644 index 0000000000000..98316b4693533 --- /dev/null +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { UiSettingsClientCommon } from './ui_settings_client_common'; +import { UiSettingsServiceOptions } from '../types'; + +export class UiSettingsClient extends UiSettingsClientCommon { + constructor(options: UiSettingsServiceOptions) { + super(options); + } +} diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client_common.ts similarity index 85% rename from packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts rename to packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client_common.ts index 908607463c4cd..1f66d05dc0a4c 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client_common.ts @@ -6,25 +6,13 @@ * Side Public License, v 1. */ -import type { Logger } from '@kbn/logging'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; -import type { UiSettingsParams } from '@kbn/core-ui-settings-common'; -import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; -import { CannotOverrideError } from './ui_settings_errors'; -import { Cache } from './cache'; +import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config'; +import { CannotOverrideError } from '../ui_settings_errors'; +import { Cache } from '../cache'; +import { UiSettingsServiceOptions } from '../types'; import { BaseUiSettingsClient } from './base_ui_settings_client'; -export interface UiSettingsServiceOptions { - type: string; - id: string; - buildNum: number; - savedObjectsClient: SavedObjectsClientContract; - overrides?: Record; - defaults?: Record; - log: Logger; -} - interface ReadOptions { autoCreateOrUpgradeIfMissing?: boolean; } @@ -36,7 +24,10 @@ interface UserProvidedValue { type UserProvided = Record>; -export class UiSettingsClient extends BaseUiSettingsClient { +/** + * Common logic for setting / removing keys in a {@link IUiSettingsClient} implementation + */ +export abstract class UiSettingsClientCommon extends BaseUiSettingsClient { private readonly type: UiSettingsServiceOptions['type']; private readonly id: UiSettingsServiceOptions['id']; private readonly buildNum: UiSettingsServiceOptions['buildNum']; @@ -44,9 +35,8 @@ export class UiSettingsClient extends BaseUiSettingsClient { private readonly cache: Cache; constructor(options: UiSettingsServiceOptions) { - const { type, id, buildNum, savedObjectsClient, log, defaults = {}, overrides = {} } = options; - super({ overrides, defaults, log }); - + super(options); + const { savedObjectsClient, type, id, buildNum } = options; this.type = type; this.id = id; this.buildNum = buildNum; @@ -97,7 +87,7 @@ export class UiSettingsClient extends BaseUiSettingsClient { } private assertUpdateAllowed(key: string) { - if (this.isOverridden(key)) { + if (this.overrides.hasOwnProperty(key)) { throw new CannotOverrideError(`Unable to update "${key}" because it is overridden`); } } @@ -117,7 +107,7 @@ export class UiSettingsClient extends BaseUiSettingsClient { // validate value read from saved objects as it can be changed via SO API const filteredValues: UserProvided = {}; for (const [key, userValue] of Object.entries(values)) { - if (userValue === null || this.isOverridden(key)) continue; + if (userValue === null || this.overrides.hasOwnProperty(key)) continue; try { this.validateKey(key, userValue); filteredValues[key] = { @@ -151,6 +141,7 @@ export class UiSettingsClient extends BaseUiSettingsClient { buildNum: this.buildNum, log: this.log, handleWriteErrors: false, + type: this.type, }); await this.write({ @@ -174,6 +165,7 @@ export class UiSettingsClient extends BaseUiSettingsClient { buildNum: this.buildNum, log: this.log, handleWriteErrors: true, + type: this.type, }); if (!failedUpgradeAttributes) { diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client_factory.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client_factory.test.ts new file mode 100644 index 0000000000000..1c9d385b33812 --- /dev/null +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client_factory.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { UiSettingsClientFactory } from './ui_settings_client_factory'; +import { UiSettingsGlobalClient } from './ui_settings_global_client'; +import { UiSettingsClient } from './ui_settings_client'; + +describe('ui settings factory', () => { + const logger = loggingSystemMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + + const createOptions = (type: 'config' | 'config-global') => { + return { + type, + id: '123', + buildNum: 1337, + savedObjectsClient, + log: logger.get(), + }; + }; + + it('instantiates the appropriate class', () => { + let options = createOptions('config-global'); + expect(UiSettingsClientFactory.create(options)).toBeInstanceOf(UiSettingsGlobalClient); + + options = createOptions('config'); + expect(UiSettingsClientFactory.create(options)).toBeInstanceOf(UiSettingsClient); + }); + + it('throws an error if not a supported type', () => { + const options = createOptions('config-global'); + expect(() => { + // @ts-expect-error + UiSettingsClientFactory.create({ ...options, type: 'unsupported' }); + }).toThrow(); + }); +}); diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client_factory.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client_factory.ts new file mode 100644 index 0000000000000..db1c69a6074fd --- /dev/null +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_client_factory.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { UiSettingsServiceOptions } from '@kbn/core-ui-settings-server-internal'; +import { UiSettingsClient } from './ui_settings_client'; +import { UiSettingsGlobalClient } from './ui_settings_global_client'; + +export class UiSettingsClientFactory { + public static create = (options: UiSettingsServiceOptions) => { + switch (options.type) { + case 'config': + return new UiSettingsClient(options); + case 'config-global': + return new UiSettingsGlobalClient(options); + default: + throw new Error('Unsupported client error'); + } + }; +} diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_defaults_client.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_defaults_client.test.ts similarity index 100% rename from packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_defaults_client.test.ts rename to packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_defaults_client.test.ts diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_defaults_client.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_defaults_client.ts similarity index 100% rename from packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_defaults_client.ts rename to packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_defaults_client.ts diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.test.ts new file mode 100644 index 0000000000000..2e4988f20c9df --- /dev/null +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.test.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { UiSettingsParams } from '@kbn/core-ui-settings-common'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { UiSettingsGlobalClient } from './ui_settings_global_client'; + +const logger = loggingSystemMock.create().get(); + +const TYPE = 'config-global'; +const ID = 'kibana-version'; +const BUILD_NUM = 1234; + +interface SetupOptions { + defaults?: Record; + esDocSource?: Record; + overrides?: Record; +} + +describe('ui settings global client', () => { + const settingA: UiSettingsParams = { + name: 'settingA', + value: 'abc', + scope: 'global', + schema: schema.string(), + }; + const settingB: UiSettingsParams = { + name: 'settingB', + value: 123, + scope: 'global', + schema: schema.number(), + }; + const settingC: UiSettingsParams = { + name: 'settingC', + value: false, + scope: 'global', + schema: schema.boolean(), + }; + const defaults = { + settingA, + settingB, + settingC, + }; + function setup(options: SetupOptions = {}) { + const { overrides = {}, esDocSource = {} } = options; + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockReturnValue({ attributes: esDocSource } as any); + + const uiSettingsClient = new UiSettingsGlobalClient({ + type: TYPE, + id: ID, + buildNum: BUILD_NUM, + defaults, + savedObjectsClient, + overrides, + log: logger, + }); + + return { + uiSettingsClient, + savedObjectsClient, + }; + } + + afterEach(() => jest.clearAllMocks()); + + describe('#set()', () => { + it('throws an error if setting is not registered', async () => { + const { uiSettingsClient } = setup(); + const setUnregisteredSetting = async () => { + await uiSettingsClient.set('settingD', 'cde'); + }; + expect(setUnregisteredSetting).rejects.toThrow( + 'Global setting settingD is not registered. Global settings need to be registered before they can be set' + ); + }); + + it('sets a value of a registered setting', async () => { + const { uiSettingsClient, savedObjectsClient } = setup(); + await uiSettingsClient.set('settingA', 'cde'); + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.update).toHaveBeenCalledWith(TYPE, ID, { + settingA: 'cde', + }); + }); + }); + + describe('#set many()', () => { + it('throws an error if any of the settings are not registered', async () => { + const { uiSettingsClient, savedObjectsClient } = setup(); + const setSettings = async () => { + await uiSettingsClient.setMany({ settingZ: 'cde', settingC: true }); + }; + expect(setSettings).rejects.toThrow( + 'Global setting settingZ is not registered. Global settings need to be registered before they can be set' + ); + expect(savedObjectsClient.update).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts new file mode 100644 index 0000000000000..71899467006bc --- /dev/null +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/clients/ui_settings_global_client.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { UiSettingsClientCommon } from './ui_settings_client_common'; +import { UiSettingsServiceOptions } from '../types'; +import { SettingNotRegisteredError } from '../ui_settings_errors'; + +/** + * Global UiSettingsClient + */ +export class UiSettingsGlobalClient extends UiSettingsClientCommon { + constructor(options: UiSettingsServiceOptions) { + super(options); + } + + async setMany(changes: Record) { + const registeredSettings = super.getRegistered(); + Object.keys(changes).forEach((key) => { + if (!registeredSettings[key]) { + throw new SettingNotRegisteredError(key); + } + }); + return super.setMany(changes); + } + + async set(key: string, value: any) { + const registeredSettings = super.getRegistered(); + if (!registeredSettings[key]) { + throw new SettingNotRegisteredError(key); + } + await super.set(key, value); + } +} diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts index eb47729070501..606c6d98538af 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts @@ -23,13 +23,13 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () { const prevVersion = '4.0.0'; const buildNum = 1337; - function setup() { + function setup(configType = 'config' as 'config' | 'config-global') { const logger = loggingSystemMock.create(); const savedObjectsClient = savedObjectsClientMock.create(); savedObjectsClient.create.mockImplementation( async (type, _, options = {}) => ({ - type, + type: configType, id: options.id, version: 'foo', } as any) @@ -42,11 +42,16 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () { buildNum, log: logger.get(), handleWriteErrors: false, + type: configType, ...options, }); expect(mockGetUpgradeableConfig).toHaveBeenCalledTimes(1); - expect(mockGetUpgradeableConfig).toHaveBeenCalledWith({ savedObjectsClient, version }); + expect(mockGetUpgradeableConfig).toHaveBeenCalledWith({ + savedObjectsClient, + version, + type: configType, + }); return resp; } @@ -250,4 +255,66 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () { }); }); }); + + describe('config-global', () => { + it('should merge upgraded attributes with current build number in new config', async () => { + const { run, savedObjectsClient } = setup('config-global'); + + const savedAttributes = { + buildNum: buildNum - 100, + defaultIndex: 'some-index', + }; + + mockGetUpgradeableConfig.mockResolvedValue({ + id: prevVersion, + attributes: savedAttributes, + }); + + await run(); + + expect(mockGetUpgradeableConfig).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.create).toHaveBeenCalledWith( + 'config-global', + { + ...savedAttributes, + buildNum, + }, + { + id: version, + } + ); + }); + }); + + describe('upgrade config-global', () => { + it('should merge upgraded attributes with current build number in new config', async () => { + const { run, savedObjectsClient } = setup('config-global'); + + const savedAttributes = { + buildNum: buildNum - 100, + defaultIndex: 'some-index', + }; + + mockGetUpgradeableConfig.mockResolvedValue({ + id: prevVersion, + attributes: savedAttributes, + }); + + await run(); + + expect(mockGetUpgradeableConfig).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.create).toHaveBeenCalledWith( + 'config-global', + { + ...savedAttributes, + buildNum, + }, + { + id: version, + } + ); + }); + }); }); diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts index e453c9cf46f31..b6532b1c95348 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts @@ -28,17 +28,19 @@ interface Options { buildNum: number; log: Logger; handleWriteErrors: boolean; + type: 'config' | 'config-global'; } export async function createOrUpgradeSavedConfig( options: Options ): Promise | undefined> { - const { savedObjectsClient, version, buildNum, log, handleWriteErrors } = options; + const { savedObjectsClient, version, buildNum, log, handleWriteErrors, type } = options; // try to find an older config we can upgrade const upgradeableConfig = await getUpgradeableConfig({ savedObjectsClient, version, + type, }); let transformDefaults = {}; @@ -61,7 +63,7 @@ export async function createOrUpgradeSavedConfig( try { // create the new SavedConfig - await savedObjectsClient.create('config', attributes, { id: version }); + await savedObjectsClient.create(type, attributes, { id: version }); } catch (error) { if (handleWriteErrors) { if (SavedObjectsErrorHelpers.isConflictError(error)) { diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/get_upgradeable_config.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/get_upgradeable_config.test.ts index 26992ad33df46..bc6e427cd2a57 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/get_upgradeable_config.test.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/get_upgradeable_config.test.ts @@ -17,10 +17,20 @@ describe('getUpgradeableConfig', () => { saved_objects: [{ id: '7.5.0', attributes: 'foo' }], } as SavedObjectsFindResponse); - await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0', type: 'config' }); expect(savedObjectsClient.find.mock.calls[0][0].type).toBe('config'); }); + it('finds saved objects with type "config-global"', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [{ id: '8.6.0', attributes: 'bar' }], + } as SavedObjectsFindResponse); + + await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0', type: 'config-global' }); + expect(savedObjectsClient.find.mock.calls[0][0].type).toBe('config-global'); + }); + it('finds saved config with version < than Kibana version', async () => { const savedConfig = { id: '7.4.0', attributes: 'foo' }; const savedObjectsClient = savedObjectsClientMock.create(); @@ -28,7 +38,11 @@ describe('getUpgradeableConfig', () => { saved_objects: [savedConfig], } as SavedObjectsFindResponse); - const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + const result = await getUpgradeableConfig({ + savedObjectsClient, + version: '7.5.0', + type: 'config', + }); expect(result).toEqual(savedConfig); }); @@ -39,7 +53,11 @@ describe('getUpgradeableConfig', () => { saved_objects: [savedConfig], } as SavedObjectsFindResponse); - const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + const result = await getUpgradeableConfig({ + savedObjectsClient, + version: '7.5.0', + type: 'config', + }); expect(result).toEqual(savedConfig); }); @@ -50,7 +68,11 @@ describe('getUpgradeableConfig', () => { saved_objects: [savedConfig], } as SavedObjectsFindResponse); - const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + const result = await getUpgradeableConfig({ + savedObjectsClient, + version: '7.5.0', + type: 'config', + }); expect(result).toBe(null); }); @@ -61,7 +83,11 @@ describe('getUpgradeableConfig', () => { saved_objects: [savedConfig], } as SavedObjectsFindResponse); - const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + const result = await getUpgradeableConfig({ + savedObjectsClient, + version: '7.5.0', + type: 'config', + }); expect(result).toBe(null); }); @@ -71,7 +97,11 @@ describe('getUpgradeableConfig', () => { saved_objects: [], } as unknown as SavedObjectsFindResponse); - const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + const result = await getUpgradeableConfig({ + savedObjectsClient, + version: '7.5.0', + type: 'config', + }); expect(result).toBe(null); }); }); diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/get_upgradeable_config.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/get_upgradeable_config.ts index 34a62590cb4cf..dafbeaaa34cb6 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/get_upgradeable_config.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/create_or_upgrade_saved_config/get_upgradeable_config.ts @@ -24,19 +24,22 @@ export interface UpgradeableConfigAttributes extends ConfigAttributes { * @param {Object} options * @property {SavedObjectsClient} savedObjectsClient * @property {string} version + * @property {type} `config` or `config-global` * @return {Promise} */ export async function getUpgradeableConfig({ savedObjectsClient, version, + type, }: { savedObjectsClient: SavedObjectsClientContract; version: string; + type: 'config' | 'config-global'; }) { // attempt to find a config we can upgrade const { saved_objects: savedConfigs } = await savedObjectsClient.find({ - type: 'config', + type, page: 1, perPage: 1000, sortField: 'buildNum', diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/index.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/index.ts index 654816e921226..a1a4b853ecd09 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/index.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/index.ts @@ -9,10 +9,11 @@ export { uiSettingsConfig } from './ui_settings_config'; export { UiSettingsService } from './ui_settings_service'; export { CoreUiSettingsRouteHandlerContext } from './ui_settings_route_handler_context'; -export { UiSettingsClient } from './ui_settings_client'; -export type { UiSettingsServiceOptions } from './ui_settings_client'; +export { UiSettingsClient, UiSettingsGlobalClient } from './clients'; export type { InternalUiSettingsServicePreboot, InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart, + UiSettingsServiceOptions, } from './types'; +export { CannotOverrideError, SettingNotRegisteredError } from './ui_settings_errors'; diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/delete.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/delete.ts index 4147358d4e1ee..b9aed6153e796 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/delete.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/delete.ts @@ -9,8 +9,11 @@ import { schema } from '@kbn/config-schema'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-server'; +import { KibanaRequest, KibanaResponseFactory } from '@kbn/core-http-server'; import type { InternalUiSettingsRouter } from '../internal_types'; import { CannotOverrideError } from '../ui_settings_errors'; +import { InternalUiSettingsRequestHandlerContext } from '../internal_types'; const validate = { params: schema.object({ @@ -19,33 +22,47 @@ const validate = { }; export function registerDeleteRoute(router: InternalUiSettingsRouter) { - router.delete( - { path: '/api/kibana/settings/{key}', validate }, - async (context, request, response) => { - try { - const uiSettingsClient = (await context.core).uiSettings.client; - - await uiSettingsClient.remove(request.params.key); + const deleteFromRequest = async ( + uiSettingsClient: IUiSettingsClient, + context: InternalUiSettingsRequestHandlerContext, + request: KibanaRequest, unknown, unknown, 'delete'>, + response: KibanaResponseFactory + ) => { + try { + await uiSettingsClient.remove(request.params.key); - return response.ok({ - body: { - settings: await uiSettingsClient.getUserProvided(), - }, + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, }); - } catch (error) { - if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { - return response.customError({ - body: error, - statusCode: error.output.statusCode, - }); - } - - if (error instanceof CannotOverrideError) { - return response.badRequest({ body: error }); - } + } - throw error; + if (error instanceof CannotOverrideError) { + return response.badRequest({ body: error }); } + + throw error; + } + }; + router.delete( + { path: '/api/kibana/settings/{key}', validate }, + async (context, request, response) => { + const uiSettingsClient = (await context.core).uiSettings.client; + return await deleteFromRequest(uiSettingsClient, context, request, response); + } + ); + router.delete( + { path: '/api/kibana/global_settings/{key}', validate }, + async (context, request, response) => { + const uiSettingsClient = (await context.core).uiSettings.globalClient; + return await deleteFromRequest(uiSettingsClient, context, request, response); } ); } diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/get.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/get.ts index 2603526c37503..11a45ee2f87c9 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/get.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/get.ts @@ -7,29 +7,47 @@ */ import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-server'; +import { KibanaRequest, KibanaResponseFactory } from '@kbn/core-http-server'; +import { InternalUiSettingsRequestHandlerContext } from '../internal_types'; import type { InternalUiSettingsRouter } from '../internal_types'; export function registerGetRoute(router: InternalUiSettingsRouter) { + const getFromRequest = async ( + uiSettingsClient: IUiSettingsClient, + context: InternalUiSettingsRequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) => { + try { + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + throw error; + } + }; router.get( { path: '/api/kibana/settings', validate: false }, async (context, request, response) => { - try { - const uiSettingsClient = (await context.core).uiSettings.client; - return response.ok({ - body: { - settings: await uiSettingsClient.getUserProvided(), - }, - }); - } catch (error) { - if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { - return response.customError({ - body: error, - statusCode: error.output.statusCode, - }); - } - - throw error; - } + const uiSettingsClient = (await context.core).uiSettings.client; + return await getFromRequest(uiSettingsClient, context, request, response); + } + ); + router.get( + { path: '/api/kibana/global_settings', validate: false }, + async (context, request, response) => { + const uiSettingsClient = (await context.core).uiSettings.globalClient; + return await getFromRequest(uiSettingsClient, context, request, response); } ); } diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/set.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/set.ts index 61493be876ad7..9f13f86946074 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/set.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/set.ts @@ -7,9 +7,13 @@ */ import { schema, ValidationError } from '@kbn/config-schema'; - +import { KibanaRequest, KibanaResponseFactory } from '@kbn/core-http-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; -import type { InternalUiSettingsRouter } from '../internal_types'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-server'; +import type { + InternalUiSettingsRequestHandlerContext, + InternalUiSettingsRouter, +} from '../internal_types'; import { CannotOverrideError } from '../ui_settings_errors'; const validate = { @@ -22,36 +26,55 @@ const validate = { }; export function registerSetRoute(router: InternalUiSettingsRouter) { - router.post( - { path: '/api/kibana/settings/{key}', validate }, - async (context, request, response) => { - try { - const uiSettingsClient = (await context.core).uiSettings.client; + const setFromRequest = async ( + uiSettingsClient: IUiSettingsClient, + context: InternalUiSettingsRequestHandlerContext, + request: KibanaRequest< + Readonly<{} & { key: string }>, + unknown, + Readonly<{ value?: any } & {}>, + 'post' + >, + response: KibanaResponseFactory + ) => { + try { + const { key } = request.params; + const { value } = request.body; - const { key } = request.params; - const { value } = request.body; + await uiSettingsClient.set(key, value); - await uiSettingsClient.set(key, value); - - return response.ok({ - body: { - settings: await uiSettingsClient.getUserProvided(), - }, + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, }); - } catch (error) { - if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { - return response.customError({ - body: error, - statusCode: error.output.statusCode, - }); - } - - if (error instanceof CannotOverrideError || error instanceof ValidationError) { - return response.badRequest({ body: error }); - } + } - throw error; + if (error instanceof CannotOverrideError || error instanceof ValidationError) { + return response.badRequest({ body: error }); } + + throw error; + } + }; + router.post( + { path: '/api/kibana/settings/{key}', validate }, + async (context, request, response) => { + const uiSettingsClient = (await context.core).uiSettings.client; + return await setFromRequest(uiSettingsClient, context, request, response); + } + ); + router.post( + { path: '/api/kibana/global_settings/{key}', validate }, + async (context, request, response) => { + const uiSettingsClient = (await context.core).uiSettings.globalClient; + return await setFromRequest(uiSettingsClient, context, request, response); } ); } diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/set_many.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/set_many.ts index 3bbe14c4a0076..71e94ac039304 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/set_many.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/routes/set_many.ts @@ -7,10 +7,12 @@ */ import { schema, ValidationError } from '@kbn/config-schema'; - import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; +import { KibanaRequest, KibanaResponseFactory } from '@kbn/core-http-server'; +import { IUiSettingsClient } from '@kbn/core-ui-settings-server'; import type { InternalUiSettingsRouter } from '../internal_types'; import { CannotOverrideError } from '../ui_settings_errors'; +import { InternalUiSettingsRequestHandlerContext } from '../internal_types'; const validate = { body: schema.object({ @@ -19,10 +21,13 @@ const validate = { }; export function registerSetManyRoute(router: InternalUiSettingsRouter) { - router.post({ path: '/api/kibana/settings', validate }, async (context, request, response) => { + const setManyFromRequest = async ( + uiSettingsClient: IUiSettingsClient, + context: InternalUiSettingsRequestHandlerContext, + request: KibanaRequest, 'post'>, + response: KibanaResponseFactory + ) => { try { - const uiSettingsClient = (await context.core).uiSettings.client; - const { changes } = request.body; await uiSettingsClient.setMany(changes); @@ -46,5 +51,17 @@ export function registerSetManyRoute(router: InternalUiSettingsRouter) { throw error; } + }; + router.post({ path: '/api/kibana/settings', validate }, async (context, request, response) => { + const uiSettingsClient = (await context.core).uiSettings.client; + return await setManyFromRequest(uiSettingsClient, context, request, response); }); + + router.post( + { path: '/api/kibana/global_settings', validate }, + async (context, request, response) => { + const uiSettingsClient = (await context.core).uiSettings.globalClient; + return await setManyFromRequest(uiSettingsClient, context, request, response); + } + ); } diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/saved_objects/index.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/saved_objects/index.ts index 92ef9961bb908..4071c0f1bae33 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/saved_objects/index.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/saved_objects/index.ts @@ -7,6 +7,6 @@ */ export type { ConfigAttributes } from './ui_settings'; -export { uiSettingsType } from './ui_settings'; +export { uiSettingsType, uiSettingsGlobalType } from './ui_settings'; export type { TransformConfigFn } from './transforms'; export { transforms } from './transforms'; diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/saved_objects/ui_settings.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/saved_objects/ui_settings.ts index 455957c289bec..ba6abf0442497 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/saved_objects/ui_settings.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/saved_objects/ui_settings.ts @@ -43,3 +43,29 @@ export const uiSettingsType: SavedObjectsType = { }, migrations, }; + +export const uiSettingsGlobalType: SavedObjectsType = { + name: 'config-global', + hidden: true, + namespaceType: 'agnostic', + mappings: { + dynamic: false, + properties: { + buildNum: { + type: 'keyword', + }, + }, + }, + management: { + importableAndExportable: true, + getInAppUrl() { + return { + path: `/app/management/kibana/settings`, + uiCapabilitiesPath: 'advancedSettings.show', + }; + }, + getTitle(obj) { + return `Global Settings [${obj.id}]`; + }, + }, +}; diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/types.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/types.ts index 16af4b0d01d67..b47df20d50bea 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/types.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/types.ts @@ -11,6 +11,9 @@ import type { UiSettingsServiceSetup, UiSettingsServiceStart, } from '@kbn/core-ui-settings-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { UiSettingsParams } from '@kbn/core-ui-settings-common'; +import type { Logger } from '@kbn/logging'; /** @internal */ export interface InternalUiSettingsServicePreboot { @@ -25,3 +28,14 @@ export type InternalUiSettingsServiceSetup = UiSettingsServiceSetup; /** @internal */ export type InternalUiSettingsServiceStart = UiSettingsServiceStart; + +/** @internal */ +export interface UiSettingsServiceOptions { + type: 'config' | 'config-global'; + id: string; + buildNum: number; + savedObjectsClient: SavedObjectsClientContract; + overrides?: Record; + defaults?: Record; + log: Logger; +} diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_errors.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_errors.ts index 4991bfbd9685e..755ed6447c5ed 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_errors.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_errors.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +// eslint-disable-next-line max-classes-per-file export class CannotOverrideError extends Error { public cause?: Error; @@ -18,3 +19,11 @@ export class CannotOverrideError extends Error { Object.setPrototypeOf(this, CannotOverrideError.prototype); } } + +export class SettingNotRegisteredError extends Error { + constructor(key: string) { + super( + `Global setting ${key} is not registered. Global settings need to be registered before they can be set` + ); + } +} diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_route_handler_context.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_route_handler_context.ts index ca7f680c0e628..58b268040a8ca 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_route_handler_context.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_route_handler_context.ts @@ -19,6 +19,7 @@ import type { InternalUiSettingsServiceStart } from './types'; */ export class CoreUiSettingsRouteHandlerContext implements UiSettingsRequestHandlerContext { #client?: IUiSettingsClient; + #globalClient?: IUiSettingsClient; constructor( private readonly uiSettingsStart: InternalUiSettingsServiceStart, @@ -33,4 +34,13 @@ export class CoreUiSettingsRouteHandlerContext implements UiSettingsRequestHandl } return this.#client; } + + public get globalClient() { + if (this.#globalClient == null) { + this.#globalClient = this.uiSettingsStart.globalAsScopedToClient( + this.savedObjectsRouterHandlerContext.client + ); + } + return this.#globalClient; + } } diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.test.mock.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.test.mock.ts index aae25563c2b88..5a32208407b48 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.test.mock.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.test.mock.ts @@ -7,12 +7,17 @@ */ export const MockUiSettingsClientConstructor = jest.fn(); -jest.doMock('./ui_settings_client', () => ({ +jest.doMock('./clients/ui_settings_client', () => ({ UiSettingsClient: MockUiSettingsClientConstructor, })); +export const MockUiSettingsGlobalClientConstructor = jest.fn(); +jest.doMock('./clients/ui_settings_global_client', () => ({ + UiSettingsGlobalClient: MockUiSettingsGlobalClientConstructor, +})); + export const MockUiSettingsDefaultsClientConstructor = jest.fn(); -jest.doMock('./ui_settings_defaults_client', () => ({ +jest.doMock('./clients/ui_settings_defaults_client', () => ({ UiSettingsDefaultsClient: MockUiSettingsDefaultsClientConstructor, })); diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.test.ts index 2cfee201609d1..23438e2ab74af 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.test.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.test.ts @@ -13,14 +13,15 @@ import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { httpServiceMock } from '@kbn/core-http-server-mocks'; import { MockUiSettingsClientConstructor, + MockUiSettingsGlobalClientConstructor, MockUiSettingsDefaultsClientConstructor, getCoreSettingsMock, } from './ui_settings_service.test.mock'; import { UiSettingsService, SetupDeps } from './ui_settings_service'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; -import { uiSettingsType } from './saved_objects'; -import { UiSettingsDefaultsClient } from './ui_settings_defaults_client'; +import { uiSettingsType, uiSettingsGlobalType } from './saved_objects'; +import { UiSettingsDefaultsClient } from './clients/ui_settings_defaults_client'; const overrides = { overrideBaz: 'baz', @@ -53,6 +54,7 @@ describe('uiSettings', () => { afterEach(() => { MockUiSettingsClientConstructor.mockClear(); + MockUiSettingsGlobalClientConstructor.mockClear(); getCoreSettingsMock.mockClear(); }); @@ -82,8 +84,9 @@ describe('uiSettings', () => { it('registers the uiSettings type to the savedObjects registry', async () => { await service.setup(setupDeps); - expect(setupDeps.savedObjects.registerType).toHaveBeenCalledTimes(1); + expect(setupDeps.savedObjects.registerType).toHaveBeenCalledTimes(2); expect(setupDeps.savedObjects.registerType).toHaveBeenCalledWith(uiSettingsType); + expect(setupDeps.savedObjects.registerType).toHaveBeenCalledWith(uiSettingsGlobalType); }); describe('#register', () => { @@ -94,6 +97,20 @@ describe('uiSettings', () => { `"uiSettings for the key [foo] has been already registered"` ); }); + + it('throws if registers the same key twice to global settings', async () => { + const setup = await service.setup(setupDeps); + setup.registerGlobal(defaults); + expect(() => setup.registerGlobal(defaults)).toThrowErrorMatchingInlineSnapshot( + `"Global uiSettings for the key [foo] has been already registered"` + ); + }); + + it('does not throw when registering a global and namespaced setting with the same name', async () => { + const setup = await service.setup(setupDeps); + setup.register(defaults); + expect(() => setup.registerGlobal(defaults)).not.toThrow(); + }); }); }); @@ -117,6 +134,20 @@ describe('uiSettings', () => { ); }); + it('throws if validation schema is not provided for global settings', async () => { + const { registerGlobal } = await service.setup(setupDeps); + registerGlobal({ + // @ts-expect-error schema is required key + custom: { + value: 42, + }, + }); + + await expect(service.start()).rejects.toMatchInlineSnapshot( + `[Error: Validation schema is not provided for [custom] Global UI Setting]` + ); + }); + it('validates registered definitions', async () => { const { register } = await service.setup(setupDeps); register({ @@ -131,6 +162,20 @@ describe('uiSettings', () => { ); }); + it('validates registered definitions for global settings', async () => { + const { registerGlobal } = await service.setup(setupDeps); + registerGlobal({ + custom: { + value: 42, + schema: schema.string(), + }, + }); + + await expect(service.start()).rejects.toMatchInlineSnapshot( + `[Error: expected value of type [string] but got [number]]` + ); + }); + it('validates overrides', async () => { const coreContext = mockCoreContext.create(); coreContext.configService.atPath.mockReturnValueOnce( @@ -200,5 +245,35 @@ describe('uiSettings', () => { expect(MockUiSettingsClientConstructor.mock.calls[0][0].defaults).not.toBe(defaults); }); }); + + describe('#asScopedToGlobalClient', () => { + it('passes saved object type "config-global" to UiSettingsGlobalClient', async () => { + await service.setup(setupDeps); + const start = await service.start(); + start.globalAsScopedToClient(savedObjectsClient); + + expect(MockUiSettingsGlobalClientConstructor).toBeCalledTimes(1); + expect(MockUiSettingsGlobalClientConstructor.mock.calls[0][0].type).toBe('config-global'); + }); + + it('passes overrides to UiSettingsGlobalClient', async () => { + await service.setup(setupDeps); + const start = await service.start(); + start.globalAsScopedToClient(savedObjectsClient); + + expect(MockUiSettingsGlobalClientConstructor).toBeCalledTimes(1); + expect(MockUiSettingsGlobalClientConstructor.mock.calls[0][0].overrides).toEqual({}); + }); + + it('passes a copy of set defaults to UiSettingsGlobalClient', async () => { + const setup = await service.setup(setupDeps); + setup.register(defaults); + const start = await service.start(); + start.globalAsScopedToClient(savedObjectsClient); + + expect(MockUiSettingsGlobalClientConstructor).toBeCalledTimes(1); + expect(MockUiSettingsGlobalClientConstructor.mock.calls[0][0].defaults).toEqual({}); + }); + }); }); }); diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.ts index 173547c8bda2e..77d494188d20a 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_service.ts @@ -14,24 +14,29 @@ import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import type { InternalSavedObjectsServiceSetup } from '@kbn/core-saved-objects-server-internal'; -import type { UiSettingsParams } from '@kbn/core-ui-settings-common'; +import type { UiSettingsParams, UiSettingsScope } from '@kbn/core-ui-settings-common'; import { UiSettingsConfigType, uiSettingsConfig as uiConfigDefinition } from './ui_settings_config'; -import { UiSettingsClient } from './ui_settings_client'; +import { UiSettingsClient, UiSettingsClientFactory, UiSettingsGlobalClient } from './clients'; import type { InternalUiSettingsServicePreboot, InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart, } from './types'; import type { InternalUiSettingsRequestHandlerContext } from './internal_types'; -import { uiSettingsType } from './saved_objects'; +import { uiSettingsType, uiSettingsGlobalType } from './saved_objects'; import { registerRoutes } from './routes'; import { getCoreSettings } from './settings'; -import { UiSettingsDefaultsClient } from './ui_settings_defaults_client'; +import { UiSettingsDefaultsClient } from './clients/ui_settings_defaults_client'; export interface SetupDeps { http: InternalHttpServiceSetup; savedObjects: InternalSavedObjectsServiceSetup; } +type ClientType = T extends 'global' + ? UiSettingsGlobalClient + : T extends 'namespace' + ? UiSettingsClient + : never; /** @internal */ export class UiSettingsService @@ -41,6 +46,7 @@ export class UiSettingsService private readonly config$: Observable; private readonly isDist: boolean; private readonly uiSettingsDefaults = new Map(); + private readonly uiSettingsGlobalDefaults = new Map(); private overrides: Record = {}; constructor(private readonly coreContext: CoreContext) { @@ -71,13 +77,15 @@ export class UiSettingsService this.log.debug('Setting up ui settings service'); savedObjects.registerType(uiSettingsType); + savedObjects.registerType(uiSettingsGlobalType); registerRoutes(http.createRouter('')); const config = await firstValueFrom(this.config$); this.overrides = config.overrides; return { - register: this.register.bind(this), + register: this.register, + registerGlobal: this.registerGlobal, }; } @@ -86,36 +94,52 @@ export class UiSettingsService this.validatesOverrides(); return { - asScopedToClient: this.getScopedClientFactory(), + asScopedToClient: this.getScopedClientFactory('namespace'), + globalAsScopedToClient: this.getScopedClientFactory('global'), }; } public async stop() {} - private getScopedClientFactory(): ( - savedObjectsClient: SavedObjectsClientContract - ) => UiSettingsClient { + private getScopedClientFactory( + scope: UiSettingsScope + ): (savedObjectsClient: SavedObjectsClientContract) => ClientType { const { version, buildNum } = this.coreContext.env.packageInfo; - return (savedObjectsClient: SavedObjectsClientContract) => - new UiSettingsClient({ - type: 'config', + return (savedObjectsClient: SavedObjectsClientContract): ClientType => { + const isNamespaceScope = scope === 'namespace'; + + const options = { + type: (isNamespaceScope ? 'config' : 'config-global') as 'config' | 'config-global', id: version, buildNum, savedObjectsClient, - defaults: mapToObject(this.uiSettingsDefaults), - overrides: this.overrides, + defaults: isNamespaceScope + ? mapToObject(this.uiSettingsDefaults) + : mapToObject(this.uiSettingsGlobalDefaults), + overrides: isNamespaceScope ? this.overrides : {}, log: this.log, - }); + }; + return UiSettingsClientFactory.create(options) as ClientType; + }; } - private register(settings: Record = {}) { + private register = (settings: Record = {}) => { Object.entries(settings).forEach(([key, value]) => { if (this.uiSettingsDefaults.has(key)) { throw new Error(`uiSettings for the key [${key}] has been already registered`); } this.uiSettingsDefaults.set(key, value); }); - } + }; + + private registerGlobal = (settings: Record = {}) => { + Object.entries(settings).forEach(([key, value]) => { + if (this.uiSettingsGlobalDefaults.has(key)) { + throw new Error(`Global uiSettings for the key [${key}] has been already registered`); + } + this.uiSettingsGlobalDefaults.set(key, value); + }); + }; private validatesDefinitions() { for (const [key, definition] of this.uiSettingsDefaults) { @@ -124,6 +148,12 @@ export class UiSettingsService } definition.schema.validate(definition.value, {}, `ui settings defaults [${key}]`); } + for (const [key, definition] of this.uiSettingsGlobalDefaults) { + if (!definition.schema) { + throw new Error(`Validation schema is not provided for [${key}] Global UI Setting`); + } + definition.schema.validate(definition.value, {}); + } } private validatesOverrides() { diff --git a/packages/core/ui-settings/core-ui-settings-server-mocks/src/ui_settings_service.mock.ts b/packages/core/ui-settings/core-ui-settings-server-mocks/src/ui_settings_service.mock.ts index 8a8c3fddd1765..5d4b4c62ebb1e 100644 --- a/packages/core/ui-settings/core-ui-settings-server-mocks/src/ui_settings_service.mock.ts +++ b/packages/core/ui-settings/core-ui-settings-server-mocks/src/ui_settings_service.mock.ts @@ -48,6 +48,7 @@ const createPrebootMock = () => { const createSetupMock = () => { const mocked: jest.Mocked = { register: jest.fn(), + registerGlobal: jest.fn(), }; return mocked; @@ -56,6 +57,7 @@ const createSetupMock = () => { const createStartMock = () => { const mocked: jest.Mocked = { asScopedToClient: jest.fn(), + globalAsScopedToClient: jest.fn(), }; mocked.asScopedToClient.mockReturnValue(createClientMock()); diff --git a/packages/core/ui-settings/core-ui-settings-server/src/contracts.ts b/packages/core/ui-settings/core-ui-settings-server/src/contracts.ts index b078da6a4ec46..2048f2fb790b1 100644 --- a/packages/core/ui-settings/core-ui-settings-server/src/contracts.ts +++ b/packages/core/ui-settings/core-ui-settings-server/src/contracts.ts @@ -13,7 +13,7 @@ import type { IUiSettingsClient } from './ui_settings_client'; /** @public */ export interface UiSettingsServiceSetup { /** - * Sets settings with default values for the uiSettings. + * Sets settings with default values for the uiSettings * @param settings * * @example @@ -30,6 +30,24 @@ export interface UiSettingsServiceSetup { * ``` */ register(settings: Record): void; + /** + * Sets settings with default values for the global uiSettings + * @param settings + * + * @example + * ```ts + * setup(core: CoreSetup){ + * core.uiSettings.register([{ + * foo: { + * name: i18n.translate('my foo settings'), + * value: true, + * description: 'add some awesomeness', + * }, + * }]); + * } + * ``` + */ + registerGlobal(settings: Record): void; } /** @public */ @@ -49,4 +67,20 @@ export interface UiSettingsServiceStart { * ``` */ asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; + + /** + * Creates a global {@link IUiSettingsClient} with provided *scoped* saved objects client. + * + * This should only be used in the specific case where the client needs to be accessed + * from outside of the scope of a {@link RequestHandler}. + * + * @example + * ```ts + * start(core: CoreStart) { + * const soClient = core.savedObjects.getScopedClient(arbitraryRequest); + * const uiSettingsClient = core.uiSettings.asScopedToGlobalClient(soClient); + * } + * ``` + */ + globalAsScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; } diff --git a/packages/core/ui-settings/core-ui-settings-server/src/request_handler_context.ts b/packages/core/ui-settings/core-ui-settings-server/src/request_handler_context.ts index 32c59539abf94..ed507e6eca2cd 100644 --- a/packages/core/ui-settings/core-ui-settings-server/src/request_handler_context.ts +++ b/packages/core/ui-settings/core-ui-settings-server/src/request_handler_context.ts @@ -14,4 +14,5 @@ import type { IUiSettingsClient } from './ui_settings_client'; */ export interface UiSettingsRequestHandlerContext { client: IUiSettingsClient; + globalClient: IUiSettingsClient; } diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/apm_fields.ts b/packages/kbn-apm-synthtrace/src/lib/apm/apm_fields.ts index 1d11dc1d7f3b3..494f270761a26 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/apm_fields.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/apm_fields.ts @@ -41,14 +41,37 @@ export interface Observer { version_major: number; } +export interface GeoLocation { + coordinates: number[]; + type: string; +} + export type ApmFields = Fields & Partial<{ 'timestamp.us'?: number; 'agent.name': string; 'agent.version': string; + 'client.geo.city_name': string; + 'client.geo.continent_name': string; + 'client.geo.country_iso_code': string; + 'client.geo.country_name': string; + 'client.geo.region_iso_code': string; + 'client.geo.region_name': string; + 'client.geo.location': GeoLocation; + 'client.ip': string; + 'cloud.provider': string; + 'cloud.project.name': string; + 'cloud.service.name': string; + 'cloud.availability_zone': string; + 'cloud.machine.type': string; + 'cloud.region': string; 'container.id': string; 'destination.address': string; 'destination.port': number; + 'device.id': string; + 'device.model.identifier': string; + 'device.model.name': string; + 'device.manufacturer': string; 'ecs.version': string; 'event.outcome': string; 'event.ingested': number; @@ -56,18 +79,36 @@ export type ApmFields = Fields & 'error.exception': ApmException[]; 'error.grouping_name': string; 'error.grouping_key': string; + 'faas.id': string; + 'faas.name': string; + 'faas.coldstart': boolean; + 'faas.execution': string; + 'faas.trigger.type': string; + 'faas.trigger.request_id': string; 'host.name': string; 'host.architecture': string; 'host.hostname': string; + 'host.os.full': string; + 'host.os.name': string; + 'host.os.platform': string; + 'host.os.type': string; + 'host.os.version': string; 'http.request.method': string; 'http.response.status_code': number; 'kubernetes.pod.uid': string; 'kubernetes.pod.name': string; 'metricset.name': string; observer: Observer; + 'network.connection.type': string; + 'network.connection.subtype': string; + 'network.carrier.name': string; + 'network.carrier.mcc': string; + 'network.carrier.mnc': string; + 'network.carrier.icc': string; 'parent.id': string; 'processor.event': string; 'processor.name': string; + 'session.id': string; 'trace.id': string; 'transaction.name': string; 'transaction.type': string; @@ -86,6 +127,7 @@ export type ApmFields = Fields & 'service.runtime.name': string; 'service.runtime.version': string; 'service.framework.name': string; + 'service.framework.version': string; 'service.target.name': string; 'service.target.type': string; 'span.action': string; @@ -103,18 +145,12 @@ export type ApmFields = Fields & trace: { id: string }; span: { id: string }; }>; - 'cloud.provider': string; - 'cloud.project.name': string; - 'cloud.service.name': string; - 'cloud.availability_zone': string; - 'cloud.machine.type': string; - 'cloud.region': string; - 'host.os.platform': string; - 'faas.id': string; - 'faas.name': string; - 'faas.coldstart': boolean; - 'faas.execution': string; - 'faas.trigger.type': string; - 'faas.trigger.request_id': string; + 'url.original': string; }> & ApmApplicationMetricFields; + +export type SpanParams = { + spanName: string; + spanType: string; + spanSubtype?: string; +} & ApmFields; diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/index.ts b/packages/kbn-apm-synthtrace/src/lib/apm/index.ts index 84e6bfc9e8126..ae8b148b412a2 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/index.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ import { service } from './service'; +import { mobileApp } from './mobile_app'; import { browser } from './browser'; import { serverlessFunction } from './serverless_function'; import { getTransactionMetrics } from './processors/get_transaction_metrics'; @@ -20,6 +21,7 @@ import type { ApmException } from './apm_fields'; export const apm = { service, + mobileApp, browser, getTransactionMetrics, getSpanDestinationMetrics, diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts b/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts index 9a7fff73b64a7..f0766301b238e 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts @@ -11,13 +11,7 @@ import { Entity } from '../entity'; import { Metricset } from './metricset'; import { Span } from './span'; import { Transaction } from './transaction'; -import { ApmApplicationMetricFields, ApmFields } from './apm_fields'; - -export type SpanParams = { - spanName: string; - spanType: string; - spanSubtype?: string; -} & ApmFields; +import { ApmApplicationMetricFields, ApmFields, SpanParams } from './apm_fields'; export class Instance extends Entity { transaction( diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/mobile_app.ts b/packages/kbn-apm-synthtrace/src/lib/apm/mobile_app.ts new file mode 100644 index 0000000000000..07753a0944f36 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/lib/apm/mobile_app.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Entity } from '../entity'; +import { ApmFields } from './apm_fields'; +import { MobileDevice } from './mobile_device'; +import { generateLongId } from '../utils/generate_id'; + +type MobileAgentName = 'android/java' | 'iOS/swift'; + +export class MobileApp extends Entity { + mobileDevice(deviceId?: string) { + return new MobileDevice({ + ...this.fields, + 'device.id': deviceId ? deviceId : generateLongId(), + 'service.language.name': this.fields['agent.name'] === 'iOS' ? 'swift' : 'java', + 'service.version': this.fields['service.version'] ?? '1.0', + }).startNewSession(); + } +} + +export function mobileApp(name: string, environment: string, agentName: MobileAgentName): MobileApp; + +export function mobileApp(options: { + name: string; + environment: string; + agentName: MobileAgentName; +}): MobileApp; + +export function mobileApp( + ...args: + | [{ name: string; environment: string; agentName: MobileAgentName }] + | [string, string, MobileAgentName] +) { + const [name, environment, agentName] = + args.length === 1 ? [args[0].name, args[0].environment, args[0].agentName] : args; + + return new MobileApp({ + 'service.name': name, + 'service.environment': environment, + 'agent.name': agentName, + }); +} diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/mobile_device.ts b/packages/kbn-apm-synthtrace/src/lib/apm/mobile_device.ts new file mode 100644 index 0000000000000..8f79523f1ec3b --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/lib/apm/mobile_device.ts @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Entity } from '../entity'; +import { Span } from './span'; +import { Transaction } from './transaction'; +import { ApmFields, SpanParams, GeoLocation } from './apm_fields'; +import { generateLongId } from '../utils/generate_id'; + +export interface DeviceInfo { + manufacturer: string; + modelIdentifier: string; + modelName?: string; +} + +export interface OSInfo { + osType: 'ios' | 'android'; + osVersion: string; + osFull?: string; + runtimeVersion?: string; +} + +export interface NetworkConnectionInfo { + type: 'unavailable' | 'wifi' | 'wired' | 'cell' | 'unknown'; + subType?: string; + carrierName?: string; + carrierMCC?: string; + carrierMNC?: string; + carrierICC?: string; +} + +export interface GeoInfo { + clientIp: string; + cityName?: string; + continentName?: string; + countryIsoCode?: string; + countryName?: string; + regionName?: string; + regionIsoCode?: string; + location?: GeoLocation; +} + +export class MobileDevice extends Entity { + networkConnection: NetworkConnectionInfo; + + constructor(public readonly fields: ApmFields) { + super(fields); + this.networkConnection = { type: 'unavailable' }; + } + + deviceInfo(...options: [DeviceInfo] | [string, string] | [string, string, string]) { + let manufacturer: string; + let modelIdentifier: string; + let modelName: string | undefined; + if (options.length === 3) { + manufacturer = options[0]; + modelIdentifier = options[1]; + modelName = options[2]; + } else if (options.length === 2) { + manufacturer = options[0]; + modelIdentifier = options[1]; + } else { + manufacturer = options[0].manufacturer; + modelIdentifier = options[0].modelIdentifier; + modelName = options[0].modelName; + } + + this.fields['device.manufacturer'] = manufacturer; + this.fields['device.model.identifier'] = modelIdentifier; + this.fields['device.model.name'] = modelName; + return this; + } + + osInfo( + ...options: + | [OSInfo] + | [string, string] + | [string, string, string] + | [string, string, string, string] + ) { + let osType: string; + let osVersion: string; + let osFull: string | undefined; + let runtimeVersion: string | undefined; + if (options.length === 4) { + osType = options[0]; + osVersion = options[1]; + osFull = options[2]; + runtimeVersion = options[3]; + } else if (options.length === 3) { + osType = options[0]; + osVersion = options[1]; + osFull = options[2]; + } else if (options.length === 2) { + osType = options[0]; + osVersion = options[1]; + } else { + osType = options[0].osType; + osVersion = options[0].osVersion; + osFull = options[0].osFull; + runtimeVersion = options[0].runtimeVersion; + } + + this.fields['host.os.type'] = osType; + this.fields['host.os.name'] = osType === 'ios' ? 'iOS' : 'Android'; + this.fields['host.os.version'] = osVersion; + this.fields['host.os.full'] = osFull; + this.fields['service.runtime.name'] = osType === 'ios' ? 'iOS' : 'Android Runtime'; + this.fields['service.runtime.version'] = runtimeVersion ?? osVersion; + return this; + } + + startNewSession() { + this.fields['session.id'] = generateLongId(); + return this; + } + + setNetworkConnection(networkInfo: NetworkConnectionInfo) { + this.networkConnection = networkInfo; + return this; + } + + setGeoInfo(geoInfo: GeoInfo) { + if (geoInfo) { + this.fields['client.ip'] = geoInfo.clientIp; + this.fields['client.geo.city_name'] = geoInfo.cityName; + this.fields['client.geo.country_name'] = geoInfo.countryName; + this.fields['client.geo.country_iso_code'] = geoInfo.countryIsoCode; + this.fields['client.geo.continent_name'] = geoInfo.continentName; + this.fields['client.geo.region_name'] = geoInfo.regionName; + this.fields['client.geo.region_iso_code'] = geoInfo.regionIsoCode; + this.fields['client.geo.location'] = geoInfo.location; + } + + return this; + } + + transaction( + ...options: + | [{ transactionName: string; frameworkName?: string; frameworkVersion?: string }] + | [string] + | [string, string] + | [string, string, string] + ) { + let transactionName: string; + let frameworkName: string | undefined; + let frameworkVersion: string | undefined; + if (options.length === 3) { + transactionName = options[0]; + frameworkName = options[1]; + frameworkVersion = options[2]; + } else if (options.length === 2) { + transactionName = options[0]; + frameworkName = options[1]; + } else if (typeof options[0] === 'string') { + transactionName = options[0]; + } else { + transactionName = options[0].transactionName; + frameworkName = options[0].frameworkName; + frameworkVersion = options[0].frameworkVersion; + } + return new Transaction({ + ...this.fields, + 'transaction.name': transactionName, + 'transaction.type': 'mobile', + 'service.framework.name': frameworkName, + 'service.framework.version': frameworkVersion, + }); + } + + span(...options: [string, string] | [string, string, string] | [SpanParams]) { + let spanName: string; + let spanType: string; + let spanSubtype: string; + let fields: ApmFields; + + if (options.length === 3 || options.length === 2) { + spanName = options[0]; + spanType = options[1]; + spanSubtype = options[2] || 'unknown'; + fields = {}; + } else { + ({ spanName, spanType, spanSubtype = 'unknown', ...fields } = options[0]); + } + + return new Span({ + ...this.fields, + ...fields, + 'span.name': spanName, + 'span.type': spanType, + 'span.subtype': spanSubtype, + }); + } + + httpSpan( + ...options: + | [{ spanName: string; httpMethod: string; httpUrl: string }] + | [string, string, string] + ) { + let spanName: string; + let httpMethod: string; + let httpUrl: string; + if (options.length === 3) { + spanName = options[0]; + httpMethod = options[1]; + httpUrl = options[2]; + } else { + spanName = options[0].spanName; + httpMethod = options[0].httpMethod; + httpUrl = options[0].httpUrl; + } + + let spanParameters: SpanParams = { + spanName, + spanType: 'external', + spanSubtype: 'http', + 'http.request.method': httpMethod, + 'url.original': httpUrl, + }; + + if (this.networkConnection) { + spanParameters = { + ...spanParameters, + 'network.connection.type': this.networkConnection.type, + 'network.connection.subtype': this.networkConnection.subType, + 'network.carrier.name': this.networkConnection.carrierName, + 'network.carrier.mcc': this.networkConnection.carrierMCC, + 'network.carrier.mnc': this.networkConnection.carrierMNC, + 'network.carrier.icc': this.networkConnection.carrierICC, + }; + } + + return this.span(spanParameters); + } +} diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/span.ts b/packages/kbn-apm-synthtrace/src/lib/apm/span.ts index 99e0b1053014a..71bd1afdf7f62 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/span.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/span.ts @@ -9,8 +9,7 @@ import url from 'url'; import { BaseSpan } from './base_span'; import { generateShortId } from '../utils/generate_id'; -import { ApmFields } from './apm_fields'; -import { SpanParams } from './instance'; +import { ApmFields, SpanParams } from './apm_fields'; export class Span extends BaseSpan { constructor(fields: ApmFields) { diff --git a/packages/kbn-apm-synthtrace/src/lib/dsl/distributed_trace_client.ts b/packages/kbn-apm-synthtrace/src/lib/dsl/distributed_trace_client.ts index ceeb94f871e3a..24e3fedd21fb4 100644 --- a/packages/kbn-apm-synthtrace/src/lib/dsl/distributed_trace_client.ts +++ b/packages/kbn-apm-synthtrace/src/lib/dsl/distributed_trace_client.ts @@ -9,8 +9,9 @@ import { times } from 'lodash'; import { elasticsearchSpan, httpExitSpan, HttpMethod, redisSpan, sqliteSpan } from '../apm/span'; import { BaseSpan } from '../apm/base_span'; -import { Instance, SpanParams } from '../apm/instance'; +import { Instance } from '../apm/instance'; import { Transaction } from '../apm/transaction'; +import { SpanParams } from '../apm/apm_fields'; export class DistributedTrace { timestamp: number; diff --git a/packages/kbn-apm-synthtrace/src/scenarios/mobile.ts b/packages/kbn-apm-synthtrace/src/scenarios/mobile.ts new file mode 100644 index 0000000000000..7d5bf5649510b --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/mobile.ts @@ -0,0 +1,425 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { apm, timerange } from '../..'; +import { + DeviceInfo, + MobileDevice, + OSInfo, + GeoInfo, + NetworkConnectionInfo, +} from '../lib/apm/mobile_device'; +import { ApmFields } from '../lib/apm/apm_fields'; +import { Scenario } from '../cli/scenario'; +import { getLogger } from '../cli/utils/get_common_services'; +import { RunOptions } from '../cli/utils/parse_run_cli_flags'; +import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; + +const ENVIRONMENT = getSynthtraceEnvironment(__filename); + +type DeviceMetadata = DeviceInfo & OSInfo; + +const ANDROID_DEVICES: DeviceMetadata[] = [ + { + manufacturer: 'Samsung', + modelIdentifier: 'SM-G930F', + modelName: 'Galaxy S7', + osType: 'android', + osVersion: '10', + osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1', + runtimeVersion: '2.1.0', + }, + { + manufacturer: 'Samsung', + modelIdentifier: 'SM-G973F', + modelName: 'Galaxy S10', + osType: 'android', + osVersion: '13', + osFull: 'Android 13, API level 3, BUILD X0ETMUBU2AER2', + runtimeVersion: '2.0.0', + }, + { + manufacturer: 'Samsung', + modelIdentifier: 'SM-N950F', + modelName: 'Galaxy Note8', + osType: 'android', + osVersion: '10', + osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1', + runtimeVersion: '2.1.0', + }, + { + manufacturer: 'Huawei', + modelIdentifier: 'HUAWEI P2-0000', + osType: 'android', + osVersion: '10', + osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1', + runtimeVersion: '2.1.0', + }, + { + manufacturer: 'Huawei', + modelIdentifier: 'HUAWEI NXT-CL00', + modelName: 'Mate8', + osType: 'android', + osVersion: '10', + osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1', + runtimeVersion: '2.1.0', + }, + { + manufacturer: 'Huawei', + modelIdentifier: 'T1-701u', + modelName: 'MediaPad', + osType: 'android', + osVersion: '10', + osFull: 'Android 13, API level 29, BUILD X0ETMUBU2AER2', + runtimeVersion: '2.0.0', + }, + { + manufacturer: 'Google', + modelIdentifier: 'Pixel 3a', + modelName: 'Pixel 3a', + osType: 'android', + osVersion: '10', + osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1', + runtimeVersion: '2.1.0', + }, + { + manufacturer: 'Google', + modelIdentifier: 'Pixel 7 Pro', + modelName: 'Pixel 7 Pro', + osType: 'android', + osVersion: '10', + osFull: 'Android 10, API level 29, BUILD A022MUBU2AUD1', + runtimeVersion: '2.1.0', + }, + { + manufacturer: 'LGE', + modelIdentifier: 'LG G6', + modelName: 'LG-LS993', + osType: 'android', + osVersion: '10', + osFull: 'Android 12, API level 31, BUILD KJSJADSlKSDAA', + runtimeVersion: '1.9.0', + }, + { + manufacturer: 'LGE', + modelIdentifier: 'LG K10', + modelName: 'LG-K425', + osType: 'android', + osVersion: '10', + osFull: 'Android 12, API level 32, BUILD KJSJA342SlKSD', + runtimeVersion: '1.9.0', + }, +]; + +const APPLE_DEVICES: DeviceMetadata[] = [ + { + manufacturer: 'Apple', + modelIdentifier: 'iPhone15,2', + modelName: 'iPhone 14 Pro', + osType: 'ios', + osVersion: '16', + osFull: 'iOS 16', + }, + { + manufacturer: 'Apple', + modelIdentifier: 'iPhone14,5', + modelName: 'iPhone 13', + osType: 'ios', + osVersion: '15', + osFull: 'iOS 15', + }, + { + manufacturer: 'Apple', + modelIdentifier: 'iPhone11,8', + modelName: 'iPhone XR', + osType: 'ios', + osVersion: '11', + osFull: 'iOS 11', + }, + { + manufacturer: 'Apple', + modelIdentifier: 'iPhone9,1', + modelName: 'iPhone 7', + osType: 'ios', + osVersion: '9', + osFull: 'iOS 9', + }, + { + manufacturer: 'Apple', + modelIdentifier: 'iPhone13,2', + modelName: 'iPhone 12', + osType: 'ios', + osVersion: '13', + osFull: 'iOS 13', + }, + { + manufacturer: 'Apple', + modelIdentifier: 'iPad12,2', + modelName: 'iPad 9th Gen', + osType: 'ios', + osVersion: '12', + osFull: 'iOS 12', + }, + { + manufacturer: 'Apple', + modelIdentifier: 'iPad13,7', + modelName: 'iPad Pro 11 inch 5th Gen', + osType: 'ios', + osVersion: '13', + osFull: 'iPadOS 13', + }, + { + manufacturer: 'Apple', + modelIdentifier: 'iPad13,11', + modelName: 'iPad Pro 12.9 inch 5th Gen', + osType: 'ios', + osVersion: '13', + osFull: 'iPadOS 13', + }, + { + manufacturer: 'Apple', + modelIdentifier: 'iPad13,19', + modelName: 'iPad 10th Gen', + osType: 'ios', + osVersion: '13', + osFull: 'iPadOS 13', + }, + { + manufacturer: 'Apple', + modelIdentifier: 'Watch6,8', + modelName: 'Apple Watch Series 7 41mm case', + osType: 'ios', + osVersion: '6', + osFull: 'WatchOS 6', + }, +]; + +type GeoAndNetwork = GeoInfo & NetworkConnectionInfo; + +const GEO_AND_NETWORK: GeoAndNetwork[] = [ + { + clientIp: '223.72.43.22', + cityName: 'Beijing', + continentName: 'Asia', + countryIsoCode: 'CN', + countryName: 'China', + regionIsoCode: 'CN-BJ', + regionName: 'Beijing', + location: { coordinates: [116.3861, 39.9143], type: 'Point' }, + type: 'wifi', + }, + { + clientIp: '20.24.184.101', + cityName: 'Singapore', + continentName: 'Asia', + countryIsoCode: 'SG', + countryName: 'Singapore', + location: { coordinates: [103.8554, 1.3036], type: 'Point' }, + type: 'cell', + subType: 'edge', + carrierName: 'M1 Limited', + carrierMNC: '03', + carrierICC: 'SG', + carrierMCC: '525', + }, + { + clientIp: '178.173.228.103', + cityName: 'Tokyo', + continentName: 'Asia', + countryIsoCode: 'JP', + countryName: 'Japan', + regionIsoCode: 'JP-13', + regionName: 'Tokyo', + location: { coordinates: [139.7425, 35.6164], type: 'Point' }, + type: 'cell', + subType: 'edge', + carrierName: 'Osaka Gas Business Create Co., Ltd.', + carrierMNC: '17', + carrierICC: 'JP', + carrierMCC: '440', + }, + { + clientIp: '147.161.184.179', + cityName: 'Paris', + continentName: 'Europe', + countryIsoCode: 'FR', + countryName: 'France', + regionIsoCode: 'FR-75', + regionName: 'Paris', + location: { coordinates: [2.4075, 48.8323], type: 'Point' }, + type: 'cell', + subType: 'hspa', + carrierName: 'Altice', + carrierMNC: '09', + carrierICC: 'FR', + carrierMCC: '208', + }, + { + clientIp: '34.136.92.88', + cityName: 'Council Bluffs', + continentName: 'North America', + countryIsoCode: 'US', + countryName: 'United States', + regionIsoCode: 'US-IA', + regionName: 'Iowa', + location: { coordinates: [-95.8517, 41.2591], type: 'Point' }, + type: 'cell', + subType: 'lte', + carrierName: 'Midwest Network Solutions Hub LLC', + carrierMNC: '070', + carrierICC: 'US', + carrierMCC: '313', + }, + { + clientIp: '163.116.135.123', + cityName: 'New York', + continentName: 'North America', + countryIsoCode: 'US', + countryName: 'United States', + regionIsoCode: 'US-NY', + regionName: 'New York', + location: { coordinates: [-73.9877, 40.7425], type: 'Point' }, + type: 'cell', + subType: 'lte', + carrierName: 'Texas A&M University', + carrierMNC: '080', + carrierICC: 'US', + carrierMCC: '314', + }, + { + clientIp: '163.116.178.27', + cityName: 'Frankfurt am Main', + continentName: 'Europe', + countryIsoCode: 'DE', + countryName: 'Germany', + regionIsoCode: 'DE-HE', + regionName: 'Hesse', + location: { coordinates: [8.6843, 50.1188], type: 'Point' }, + type: 'cell', + subType: 'lte', + carrierName: 'Telekom Deutschland GmbH', + carrierMNC: '06', + carrierICC: 'DE', + carrierMCC: '262', + }, +]; + +function randomInt(max: number) { + return Math.floor(Math.random() * max); +} + +const scenario: Scenario = async (runOptions: RunOptions) => { + const logger = getLogger(runOptions); + + const { numDevices = 10 } = runOptions.scenarioOpts || {}; + + return { + generate: ({ from, to }) => { + const range = timerange(from, to); + + const androidDevices = [...Array(numDevices).keys()].map((index) => { + const deviceMetadata = ANDROID_DEVICES[randomInt(ANDROID_DEVICES.length)]; + const geoNetwork = GEO_AND_NETWORK[randomInt(GEO_AND_NETWORK.length)]; + return apm + .mobileApp({ name: 'synth-android', environment: ENVIRONMENT, agentName: 'android/java' }) + .mobileDevice() + .deviceInfo(deviceMetadata) + .osInfo(deviceMetadata) + .setGeoInfo(geoNetwork) + .setNetworkConnection(geoNetwork); + }); + + const iOSDevices = [...Array(numDevices).keys()].map((index) => { + const deviceMetadata = APPLE_DEVICES[randomInt(APPLE_DEVICES.length)]; + const geoNetwork = GEO_AND_NETWORK[randomInt(GEO_AND_NETWORK.length)]; + return apm + .mobileApp({ name: 'synth-ios', environment: ENVIRONMENT, agentName: 'iOS/swift' }) + .mobileDevice() + .deviceInfo(deviceMetadata) + .osInfo(deviceMetadata) + .setGeoInfo(geoNetwork) + .setNetworkConnection(geoNetwork); + }); + + const clickRate = range.ratePerMinute(2); + + const sessionTransactions = (device: MobileDevice) => { + return clickRate.generator((timestamp, index) => { + device.startNewSession(); + const framework = + device.fields['device.manufacturer'] === 'Apple' ? 'iOS' : 'Android Activity'; + return [ + device + .transaction('Start View - View Appearing', framework) + .timestamp(timestamp) + .duration(500) + .success() + .children( + device + .span({ + spanName: 'onCreate', + spanType: 'app', + spanSubtype: 'internal', + }) + .duration(50) + .success() + .timestamp(timestamp + 20), + device + .httpSpan({ + spanName: 'GET backend:1234', + httpMethod: 'GET', + httpUrl: 'https://backend:1234/api/start', + }) + .duration(800) + .success() + .timestamp(timestamp + 400) + ), + device + .transaction('Second View - View Appearing', framework) + .timestamp(10000 + timestamp) + .duration(300) + .success() + .children( + device + .httpSpan({ + spanName: 'GET backend:1234', + httpMethod: 'GET', + httpUrl: 'https://backend:1234/api/second', + }) + .duration(400) + .success() + .timestamp(10000 + timestamp + 250) + ), + device + .transaction('Third View - View Appearing', framework) + .timestamp(20000 + timestamp) + .duration(300) + .success() + .children( + device + .span({ + spanName: 'onCreate', + spanType: 'app', + spanSubtype: 'internal', + }) + .duration(50) + .success() + .timestamp(20000 + timestamp + 20) + ), + ]; + }); + }; + + return [...androidDevices, ...iOSDevices] + .map((device) => logger.perf('generating_apm_events', () => sessionTransactions(device))) + .reduce((p, c) => p.merge(c)); + }, + }; +}; + +export default scenario; diff --git a/packages/kbn-handlebars/.patches/basic.patch b/packages/kbn-handlebars/.patches/basic.patch index c632dd7ae746e..e41c3b1cc9a85 100644 --- a/packages/kbn-handlebars/.patches/basic.patch +++ b/packages/kbn-handlebars/.patches/basic.patch @@ -1,15 +1,5 @@ -1,11c1,21 +1c1,6 < global.handlebarsEnv = null; -< -< beforeEach(function() { -< global.handlebarsEnv = Handlebars.create(); -< }); -< -< describe('basic context', function() { -< it('most basic', function() { -< expectTemplate('{{foo}}') -< .withInput({ foo: 'foo' }) -< .toCompileTo('foo'); --- > /* > * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), @@ -17,22 +7,24 @@ > * Elasticsearch B.V. licenses this file to you under the MIT License. > * See `packages/kbn-handlebars/LICENSE` for more information. > */ -> +3,5c8,9 +< beforeEach(function() { +< global.handlebarsEnv = Handlebars.create(); +< }); +--- > import Handlebars from '../..'; > import { expectTemplate } from '../__jest__/test_bench'; -> +7,11c11,13 +< describe('basic context', function() { +< it('most basic', function() { +< expectTemplate('{{foo}}') +< .withInput({ foo: 'foo' }) +< .toCompileTo('foo'); +--- > describe('basic context', () => { > it('most basic', () => { > expectTemplate('{{foo}}').withInput({ foo: 'foo' }).toCompileTo('foo'); -> }); -> -> it('escaping', () => { -> expectTemplate('\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('{{foo}}'); -> expectTemplate('content \\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content {{foo}}'); -> expectTemplate('\\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('\\food'); -> expectTemplate('content \\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content \\food'); -> expectTemplate('\\\\ {{foo}}').withInput({ foo: 'food' }).toCompileTo('\\\\ food'); -14,36c24 +14,33c16,21 < it('escaping', function() { < expectTemplate('\\{{foo}}') < .withInput({ foo: 'food' }) @@ -53,8 +45,14 @@ < expectTemplate('\\\\ {{foo}}') < .withInput({ foo: 'food' }) < .toCompileTo('\\\\ food'); -< }); -< +--- +> it('escaping', () => { +> expectTemplate('\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('{{foo}}'); +> expectTemplate('content \\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content {{foo}}'); +> expectTemplate('\\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('\\food'); +> expectTemplate('content \\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content \\food'); +> expectTemplate('\\\\ {{foo}}').withInput({ foo: 'food' }).toCompileTo('\\\\ food'); +36c24 < it('compiling with a basic context', function() { --- > it('compiling with a basic context', () => { @@ -199,7 +197,7 @@ > it('newlines', () => { 190d150 < -194,223c154,160 +194,216c154,159 < it('escaping text', function() { < expectTemplate("Awesome's") < .withMessage( @@ -223,13 +221,6 @@ < expectTemplate(" ' ' ") < .withMessage('double quotes never produce invalid javascript') < .toCompileTo(" ' ' "); -< }); -< -< it('escaping expressions', function() { -< expectTemplate('{{{awesome}}}') -< .withInput({ awesome: "&'\\<>" }) -< .withMessage("expressions with 3 handlebars aren't escaped") -< .toCompileTo("&'\\<>"); --- > it('escaping text', () => { > expectTemplate("Awesome's").toCompileTo("Awesome's"); @@ -237,16 +228,21 @@ > expectTemplate('Awesome\\\\ foo').toCompileTo('Awesome\\\\ foo'); > expectTemplate('Awesome {{foo}}').withInput({ foo: '\\' }).toCompileTo('Awesome \\'); > expectTemplate(" ' ' ").toCompileTo(" ' ' "); -> }); -225,228c162,165 -< expectTemplate('{{&awesome}}') +219,223c162,163 +< it('escaping expressions', function() { +< expectTemplate('{{{awesome}}}') < .withInput({ awesome: "&'\\<>" }) -< .withMessage("expressions with {{& handlebars aren't escaped") +< .withMessage("expressions with 3 handlebars aren't escaped") < .toCompileTo("&'\\<>"); --- > it('escaping expressions', () => { > expectTemplate('{{{awesome}}}').withInput({ awesome: "&'\\<>" }).toCompileTo("&'\\<>"); -> +225,228c165 +< expectTemplate('{{&awesome}}') +< .withInput({ awesome: "&'\\<>" }) +< .withMessage("expressions with {{& handlebars aren't escaped") +< .toCompileTo("&'\\<>"); +--- > expectTemplate('{{&awesome}}').withInput({ awesome: "&'\\<>" }).toCompileTo("&'\\<>"); 232d168 < .withMessage('by default expressions should be escaped') diff --git a/packages/kbn-handlebars/.patches/blocks.patch b/packages/kbn-handlebars/.patches/blocks.patch index 55a1d7d2d391a..f5c68780a3654 100644 --- a/packages/kbn-handlebars/.patches/blocks.patch +++ b/packages/kbn-handlebars/.patches/blocks.patch @@ -420,7 +420,7 @@ < equals(run, true); --- > expect(run).toEqual(true); -406,408c314,321 +406,408c314,317 < describe('registration', function() { < it('unregisters', function() { < handlebarsEnv.decorators = {}; @@ -429,13 +429,13 @@ > beforeEach(() => { > global.kbnHandlebarsEnv = Handlebars.create(); > }); -> +410c319,323 +< handlebarsEnv.registerDecorator('foo', function() { +--- > it('unregisters', () => { > // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property. > kbnHandlebarsEnv!.decorators = {}; -410c323 -< handlebarsEnv.registerDecorator('foo', function() { ---- +> > kbnHandlebarsEnv!.registerDecorator('foo', function () { 414,416c327,329 < equals(!!handlebarsEnv.decorators.foo, true); @@ -445,18 +445,18 @@ > expect(!!kbnHandlebarsEnv!.decorators.foo).toEqual(true); > kbnHandlebarsEnv!.unregisterDecorator('foo'); > expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined(); -419,424c332,339 +419,420c332,334 < it('allows multiple globals', function() { < handlebarsEnv.decorators = {}; -< -< handlebarsEnv.registerDecorator({ -< foo: function() {}, -< bar: function() {} --- > it('allows multiple globals', () => { > // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property. > kbnHandlebarsEnv!.decorators = {}; -> +422,424c336,339 +< handlebarsEnv.registerDecorator({ +< foo: function() {}, +< bar: function() {} +--- > // @ts-expect-error: Expected 2 arguments, but got 1. > kbnHandlebarsEnv!.registerDecorator({ > foo() {}, diff --git a/packages/kbn-handlebars/.patches/builtins.patch b/packages/kbn-handlebars/.patches/builtins.patch index 721af39d87169..536c1ee8d7da3 100644 --- a/packages/kbn-handlebars/.patches/builtins.patch +++ b/packages/kbn-handlebars/.patches/builtins.patch @@ -341,7 +341,7 @@ > // TODO: This test has been added to the `4.x` branch of the handlebars.js repo along with a code-fix, > // but a new version of the handlebars package containing this fix has not yet been published to npm. > // -> // Before enabling this code, a new version of handlebars needs to be released and the corrosponding +> // Before enabling this code, a new version of handlebars needs to be released and the corresponding > // updates needs to be applied to this implementation. > // > // See: https://github.com/handlebars-lang/handlebars.js/commit/30dbf0478109ded8f12bb29832135d480c17e367 @@ -703,20 +703,20 @@ --- > expect('03').toEqual(levelArg); > expect('whee').toEqual(logArg); -609,616c511,521 +609,610c511,513 < it('should output to info', function() { < var called; -< +--- +> it('should output to info', function () { +> let calls = 0; +> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; +612,616c515,521 < console.info = function(info) { < equals('whee', info); < called = true; < console.info = $info; < console.log = $log; --- -> it('should output to info', function () { -> let calls = 0; -> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; -> > console.info = function (info) { > expect('whee').toEqual(info); > calls++; @@ -746,19 +746,19 @@ --- > expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo(''); > expect(calls).toEqual(callsExpected); -631,637c536,543 +631,632c536,538 < it('should log at data level', function() { < var called; -< +--- +> it('should log at data level', function () { +> let calls = 0; +> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; +634,637c540,543 < console.error = function(log) { < equals('whee', log); < called = true; < console.error = $error; --- -> it('should log at data level', function () { -> let calls = 0; -> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; -> > console.error = function (log) { > expect('whee').toEqual(log); > calls++; diff --git a/packages/kbn-handlebars/.patches/compiler.patch b/packages/kbn-handlebars/.patches/compiler.patch index f15e5df7cd85d..f1ba8aaa2b8b6 100644 --- a/packages/kbn-handlebars/.patches/compiler.patch +++ b/packages/kbn-handlebars/.patches/compiler.patch @@ -1,15 +1,24 @@ -1,92c1,24 +1,4c1,6 < describe('compiler', function() { < if (!Handlebars.compile) { < return; < } -< +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +6,10c8 < describe('#equals', function() { < function compile(string) { < var ast = Handlebars.parse(string); < return new Handlebars.Compiler().compile(ast, {}); < } -< +--- +> import Handlebars from '../..'; +12,60c10,13 < it('should treat as equal', function() { < equal(compile('foo').equals(compile('foo')), true); < equal(compile('{{foo}}').equals(compile('{{foo}}')), true); @@ -59,7 +68,12 @@ < ); < }); < }); -< +--- +> describe('compiler', () => { +> const compileFns = ['compile', 'compileAST']; +> if (process.env.AST) compileFns.splice(0, 1); +> else if (process.env.EVAL) compileFns.splice(1, 1); +62,78c15,17 < describe('#compile', function() { < it('should fail with invalid input', function() { < shouldThrow( @@ -77,7 +91,11 @@ < 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]' < ); < }); -< +--- +> compileFns.forEach((compileName) => { +> // @ts-expect-error +> const compile = Handlebars[compileName]; +80,92c19,24 < it('should include the location in the error (row and column)', function() { < try { < Handlebars.compile(' \n {{#if}}\n{{/def}}')(); @@ -92,24 +110,6 @@ < "if doesn't match def - 2:5", < 'Checking error message' --- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -> -> import Handlebars from '../..'; -> -> describe('compiler', () => { -> const compileFns = ['compile', 'compileAST']; -> if (process.env.AST) compileFns.splice(0, 1); -> else if (process.env.EVAL) compileFns.splice(1, 1); -> -> compileFns.forEach((compileName) => { -> // @ts-expect-error -> const compile = Handlebars[compileName]; -> > describe(`#${compileName}`, () => { > it('should fail with invalid input', () => { > expect(function () { @@ -160,11 +160,27 @@ < }); --- > }); -131,152c34,48 +131,133c34,48 < it('can pass through an empty string', function() { < equal(Handlebars.compile('')(), ''); < }); -< +--- +> it('should include the location in the error (row and column)', () => { +> try { +> compile(' \n {{#if}}\n{{/def}}')({}); +> expect(true).toEqual(false); +> } catch (err) { +> expect(err.message).toEqual("if doesn't match def - 2:5"); +> if (Object.getOwnPropertyDescriptor(err, 'column')!.writable) { +> // In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty, +> // its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482) +> // Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers. +> expect(err.column).toEqual(5); +> } +> expect(err.lineNumber).toEqual(2); +> } +> }); +135,142c50,57 < it('should not modify the options.data property(GH-1327)', function() { < var options = { data: [{ a: 'foo' }, { a: 'bar' }] }; < Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); @@ -173,7 +189,16 @@ < JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2) < ); < }); -< +--- +> it('should include the location as enumerable property', () => { +> try { +> compile(' \n {{#if}}\n{{/def}}')({}); +> expect(true).toEqual(false); +> } catch (err) { +> expect(Object.prototype.propertyIsEnumerable.call(err, 'column')).toEqual(true); +> } +> }); +144,152c59,66 < it('should not modify the options.knownHelpers property(GH-1327)', function() { < var options = { knownHelpers: {} }; < Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); @@ -184,22 +209,15 @@ < }); < }); --- -> it('should include the location in the error (row and column)', () => { -> try { -> compile(' \n {{#if}}\n{{/def}}')({}); -> expect(true).toEqual(false); -> } catch (err) { -> expect(err.message).toEqual("if doesn't match def - 2:5"); -> if (Object.getOwnPropertyDescriptor(err, 'column')!.writable) { -> // In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty, -> // its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482) -> // Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers. -> expect(err.column).toEqual(5); -> } -> expect(err.lineNumber).toEqual(2); -> } +> it('can utilize AST instance', () => { +> expect( +> compile({ +> type: 'Program', +> body: [{ type: 'ContentStatement', value: 'Hello' }], +> })({}) +> ).toEqual('Hello'); > }); -154,170c50,57 +154,170c68,70 < describe('#precompile', function() { < it('should fail with invalid input', function() { < shouldThrow( @@ -218,24 +236,15 @@ < ); < }); --- -> it('should include the location as enumerable property', () => { -> try { -> compile(' \n {{#if}}\n{{/def}}')({}); -> expect(true).toEqual(false); -> } catch (err) { -> expect(Object.prototype.propertyIsEnumerable.call(err, 'column')).toEqual(true); -> } +> it('can pass through an empty string', () => { +> expect(compile('')({})).toEqual(''); > }); -172,175c59,61 +172,182c72,78 < it('can utilize AST instance', function() { < equal( < /return "Hello"/.test( < Handlebars.precompile({ ---- -> it('can utilize AST instance', () => { -> expect( -> compile({ -177,182c63,78 +< type: 'Program', < body: [{ type: 'ContentStatement', value: 'Hello' }] < }) < ), @@ -243,15 +252,6 @@ < ); < }); --- -> body: [{ type: 'ContentStatement', value: 'Hello' }], -> })({}) -> ).toEqual('Hello'); -> }); -> -> it('can pass through an empty string', () => { -> expect(compile('')({})).toEqual(''); -> }); -> > it('should not modify the options.data property(GH-1327)', () => { > const options = { data: [{ a: 'foo' }, { a: 'bar' }] }; > compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)({}); diff --git a/packages/kbn-handlebars/.patches/helpers.patch b/packages/kbn-handlebars/.patches/helpers.patch index 4d25d19937468..c274bb4a69271 100644 --- a/packages/kbn-handlebars/.patches/helpers.patch +++ b/packages/kbn-handlebars/.patches/helpers.patch @@ -306,21 +306,22 @@ > helper: () => 'winning', 293a271 > // @ts-expect-error -295,298c273,275 +295,299d272 < var helpers = { < './helper': function() { < return 'fail'; < } ---- -> -> const helpers = { -> './helper': () => 'fail', -301,309c278,279 +< }; +301,304c274,276 < expectTemplate('{{./helper 1}}') < .withInput(hash) < .withHelpers(helpers) < .toCompileTo('winning'); -< +--- +> const helpers = { +> './helper': () => 'fail', +> }; +306,309c278,279 < expectTemplate('{{hash/helper 1}}') < .withInput(hash) < .withHelpers(helpers) @@ -431,19 +432,19 @@ > expect(kbnHandlebarsEnv!.helpers.foo).toBeDefined(); > kbnHandlebarsEnv!.unregisterHelper('foo'); > expect(kbnHandlebarsEnv!.helpers.foo).toBeUndefined(); -398,404c362,368 +398,400c362,364 < it('allows multiple globals', function() { < var helpers = handlebarsEnv.helpers; < handlebarsEnv.helpers = {}; -< -< handlebarsEnv.registerHelper({ -< if: helpers['if'], -< world: function() { --- > it('allows multiple globals', () => { > const ifHelper = kbnHandlebarsEnv!.helpers.if; > deleteAllKeys(kbnHandlebarsEnv!.helpers); -> +402,404c366,368 +< handlebarsEnv.registerHelper({ +< if: helpers['if'], +< world: function() { +--- > kbnHandlebarsEnv!.registerHelper({ > if: ifHelper, > world() { diff --git a/packages/kbn-handlebars/.patches/security.patch b/packages/kbn-handlebars/.patches/security.patch index 9a49b6f12a043..3eb55711c3f9c 100644 --- a/packages/kbn-handlebars/.patches/security.patch +++ b/packages/kbn-handlebars/.patches/security.patch @@ -1,14 +1,10 @@ -1,10c1,15 +1,6c1,6 < describe('security issues', function() { < describe('GH-1495: Prevent Remote Code Execution via constructor', function() { < it('should not allow constructors to be accessed', function() { < expectTemplate('{{lookup (lookup this "constructor") "name"}}') < .withInput({}) < .toCompileTo(''); -< -< expectTemplate('{{constructor.name}}') -< .withInput({}) -< .toCompileTo(''); --- > /* > * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), @@ -16,7 +12,11 @@ > * Elasticsearch B.V. licenses this file to you under the MIT License. > * See `packages/kbn-handlebars/LICENSE` for more information. > */ -> +8,10c8,15 +< expectTemplate('{{constructor.name}}') +< .withInput({}) +< .toCompileTo(''); +--- > import Handlebars from '../..'; > import { expectTemplate } from '../__jest__/test_bench'; > @@ -150,7 +150,7 @@ < '{{lookup this "__proto__"}}' --- > '{{lookup this "__proto__"}}', -144,257c111,114 +144,382c111,114 < templates.forEach(function(template) { < describe('access should be denied to ' + template, function() { < it('by default', function() { @@ -265,12 +265,7 @@ < .toCompileTo(''); < < expect(spy.callCount).to.equal(0); ---- -> templates.forEach((template) => { -> describe('access should be denied to ' + template, () => { -> it('by default', () => { -> expectTemplate(template).withInput({}).toCompileTo(''); -259,399d115 +< }); < < it('can be turned off, if turned on by default', function() { < expectTemplate('{{aMethod}}') @@ -395,8 +390,12 @@ < sinon.replace(handlebarsEnv, 'template', function(templateSpec) { < templateSpec.main = wrapToAdjustContainer(templateSpec.main); < return oldTemplateMethod.call(this, templateSpec); -< }); -< }); +--- +> templates.forEach((template) => { +> describe('access should be denied to ' + template, () => { +> it('by default', () => { +> expectTemplate(template).withInput({}).toCompileTo(''); +385,400d116 < < afterEach(function() { < sinon.restore(); @@ -412,6 +411,7 @@ < expectTemplate('{{anArray.length}}') < .withInput({ anArray: ['a', 'b', 'c'] }) < .toCompileTo('3'); +< }); 404,409c120,122 < describe('escapes template variables', function() { < it('in compat mode', function() { diff --git a/packages/kbn-handlebars/.patches/strict.patch b/packages/kbn-handlebars/.patches/strict.patch index be50113e1416d..30613348b9852 100644 --- a/packages/kbn-handlebars/.patches/strict.patch +++ b/packages/kbn-handlebars/.patches/strict.patch @@ -1,9 +1,5 @@ -1,5c1,12 +1c1,6 < var Exception = Handlebars.Exception; -< -< describe('strict', function() { -< describe('strict mode', function() { -< it('should error on missing property lookup', function() { --- > /* > * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), @@ -11,7 +7,11 @@ > * Elasticsearch B.V. licenses this file to you under the MIT License. > * See `packages/kbn-handlebars/LICENSE` for more information. > */ -> +3,5c8,12 +< describe('strict', function() { +< describe('strict mode', function() { +< it('should error on missing property lookup', function() { +--- > import { expectTemplate } from '../__jest__/test_bench'; > > describe('strict', () => { diff --git a/packages/kbn-handlebars/.patches/subexpressions.patch b/packages/kbn-handlebars/.patches/subexpressions.patch index 197f03e75b958..c775ac2035aff 100644 --- a/packages/kbn-handlebars/.patches/subexpressions.patch +++ b/packages/kbn-handlebars/.patches/subexpressions.patch @@ -214,7 +214,7 @@ < t: function(defaultString) { --- > t(defaultString) { -186,212d181 +186,242d181 < } < }) < .toCompileTo(''); @@ -242,7 +242,7 @@ < 'string params for outer helper processed correctly' < ); < return a + b; -214,231d182 +< }, < < blorg: function(a, options) { < equals( @@ -261,7 +261,7 @@ < .withInput({ < foo: {}, < yeah: {} -233,248c184 +< }) < .toCompileTo('fooyeah'); < }); < @@ -272,11 +272,11 @@ < blog: function(options) { < equals(options.hashTypes.fun, 'SubExpression'); < return 'val is ' + options.hash.fun; -< }, +244,246d182 < bork: function() { < return 'BORK'; < } -< }) +248c184 < .toCompileTo('val is BORK'); --- > .toCompileTo(''); diff --git a/packages/kbn-handlebars/.patches/utils.patch b/packages/kbn-handlebars/.patches/utils.patch index c3547b5b9d6c2..8bd09ad0c9927 100644 --- a/packages/kbn-handlebars/.patches/utils.patch +++ b/packages/kbn-handlebars/.patches/utils.patch @@ -1,4 +1,4 @@ -1,86c1,21 +1,55c1,6 < describe('utils', function() { < describe('#SafeString', function() { < it('constructing a safestring from a string and checking its type', function() { @@ -54,7 +54,14 @@ < equals(Handlebars.Utils.escapeExpression([]), [].toString()); < }); < }); -< +--- +> /* +> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), +> * and may include modifications made by Elasticsearch B.V. +> * Elasticsearch B.V. licenses this file to you under the MIT License. +> * See `packages/kbn-handlebars/LICENSE` for more information. +> */ +57,64c8,9 < describe('#isEmpty', function() { < it('should not be empty', function() { < equals(Handlebars.Utils.isEmpty(undefined), true); @@ -63,13 +70,23 @@ < equals(Handlebars.Utils.isEmpty(''), true); < equals(Handlebars.Utils.isEmpty([]), true); < }); -< +--- +> import Handlebars from '../..'; +> import { expectTemplate } from '../__jest__/test_bench'; +66,70c11,16 < it('should be empty', function() { < equals(Handlebars.Utils.isEmpty(0), false); < equals(Handlebars.Utils.isEmpty([1]), false); < equals(Handlebars.Utils.isEmpty('foo'), false); < equals(Handlebars.Utils.isEmpty({ bar: 1 }), false); -< }); +--- +> describe('utils', function () { +> describe('#SafeString', function () { +> it('constructing a safestring from a string and checking its type', function () { +> const safe = new Handlebars.SafeString('testing 1, 2, 3'); +> expect(safe).toBeInstanceOf(Handlebars.SafeString); +> expect(safe.toString()).toEqual('testing 1, 2, 3'); +72,83d17 < }); < < describe('#extend', function() { @@ -82,28 +99,10 @@ < var b = { b: 2 }; < < Handlebars.Utils.extend(b, new A()); -< +85,86c19,21 < equals(b.a, 1); < equals(b.b, 2); --- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -> -> import Handlebars from '../..'; -> import { expectTemplate } from '../__jest__/test_bench'; -> -> describe('utils', function () { -> describe('#SafeString', function () { -> it('constructing a safestring from a string and checking its type', function () { -> const safe = new Handlebars.SafeString('testing 1, 2, 3'); -> expect(safe).toBeInstanceOf(Handlebars.SafeString); -> expect(safe.toString()).toEqual('testing 1, 2, 3'); -> }); -> > it('it should not escape SafeString properties', function () { > const name = new Handlebars.SafeString('Sean O'Malley'); > expectTemplate('{{name}}').withInput({ name }).toCompileTo('Sean O'Malley'); diff --git a/packages/kbn-handlebars/.patches/whitespace-control.patch b/packages/kbn-handlebars/.patches/whitespace-control.patch index f9e32bc2260ca..f973b0b5ad8d8 100644 --- a/packages/kbn-handlebars/.patches/whitespace-control.patch +++ b/packages/kbn-handlebars/.patches/whitespace-control.patch @@ -1,4 +1,4 @@ -1,24c1,17 +1,19c1,6 < describe('whitespace control', function() { < it('should strip whitespace around mustache calls', function() { < var hash = { foo: 'bar<' }; @@ -18,11 +18,6 @@ < expectTemplate(' {{~&foo~}} ') < .withInput(hash) < .toCompileTo('bar<'); -< -< expectTemplate(' {{~{foo}~}} ') -< .withInput(hash) -< .toCompileTo('bar<'); -< --- > /* > * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), @@ -30,9 +25,13 @@ > * Elasticsearch B.V. licenses this file to you under the MIT License. > * See `packages/kbn-handlebars/LICENSE` for more information. > */ -> +21,23c8 +< expectTemplate(' {{~{foo}~}} ') +< .withInput(hash) +< .toCompileTo('bar<'); +--- > import { expectTemplate } from '../__jest__/test_bench'; -> +24a10,17 > describe('whitespace control', () => { > it('should strip whitespace around mustache calls', () => { > const hash = { foo: 'bar<' }; @@ -41,7 +40,7 @@ > expectTemplate(' {{foo~}} ').withInput(hash).toCompileTo(' bar<'); > expectTemplate(' {{~&foo~}} ').withInput(hash).toCompileTo('bar<'); > expectTemplate(' {{~{foo}~}} ').withInput(hash).toCompileTo('bar<'); -28,46c21,28 +28,42c21,23 < describe('blocks', function() { < it('should strip whitespace around simple block calls', function() { < var hash = { foo: 'bar<' }; @@ -57,15 +56,15 @@ < expectTemplate(' {{~#if foo}} bar {{~/if}} ') < .withInput(hash) < .toCompileTo(' bar '); -< -< expectTemplate(' {{#if foo}} bar {{/if}} ') -< .withInput(hash) -< .toCompileTo(' bar '); --- > describe('blocks', () => { > it('should strip whitespace around simple block calls', () => { > const hash = { foo: 'bar<' }; -> +44,46c25,28 +< expectTemplate(' {{#if foo}} bar {{/if}} ') +< .withInput(hash) +< .toCompileTo(' bar '); +--- > expectTemplate(' {{~#if foo~}} bar {{~/if~}} ').withInput(hash).toCompileTo('bar'); > expectTemplate(' {{#if foo~}} bar {{/if~}} ').withInput(hash).toCompileTo(' bar '); > expectTemplate(' {{~#if foo}} bar {{~/if}} ').withInput(hash).toCompileTo(' bar '); @@ -87,7 +86,7 @@ < ).toCompileTo('bar'); --- > expectTemplate(' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ').toCompileTo('bar'); -71,80c47,48 +71,88c47,48 < it('should strip whitespace around complex block calls', function() { < var hash = { foo: 'bar<' }; < @@ -98,34 +97,34 @@ < expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}') < .withInput(hash) < .toCompileTo('bar '); +< +< expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}') +< .withInput(hash) +< .toCompileTo(' bar'); +< +< expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}') +< .withInput(hash) +< .toCompileTo(' bar '); --- > it('should strip whitespace around complex block calls', () => { > const hash = { foo: 'bar<' }; -82,84c50,54 -< expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}') +90,92c50,54 +< expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}') < .withInput(hash) -< .toCompileTo(' bar'); +< .toCompileTo('bar'); --- > expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').withInput(hash).toCompileTo('bar'); > expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}').withInput(hash).toCompileTo('bar '); > expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}').withInput(hash).toCompileTo(' bar'); > expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}').withInput(hash).toCompileTo(' bar '); > expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').withInput(hash).toCompileTo('bar'); -86,90c56 -< expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}') -< .withInput(hash) -< .toCompileTo(' bar '); -< -< expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}') ---- -> expectTemplate('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n') -94,102c60 +94,96c56 < expectTemplate( < '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' < ) -< .withInput(hash) -< .toCompileTo('bar'); -< +--- +> expectTemplate('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n') +100,102c60 < expectTemplate( < '\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' < ) diff --git a/packages/kbn-handlebars/scripts/check_for_test_changes.sh b/packages/kbn-handlebars/scripts/check_for_test_changes.sh index 79ad59de3f9ed..a9818505b707f 100755 --- a/packages/kbn-handlebars/scripts/check_for_test_changes.sh +++ b/packages/kbn-handlebars/scripts/check_for_test_changes.sh @@ -1,10 +1,29 @@ +#!/usr/bin/env bash + set -e -rm -fr .tmp -mkdir .tmp +TMP=.tmp-handlebars + +# Try to detect Windows environment (I've not tested this!) +if [[ "$OSTYPE" == "msys" ]]; then + # Windows environment + DEVNULL=NUL +else + # Everything else (including Cygwin on Windows) + DEVNULL=/dev/null +fi + +function cleanup { + rm -fr $TMP +} + +trap cleanup EXIT + +rm -fr $TMP +mkdir $TMP echo "Cloning handlebars repo..." -git clone -q --depth 1 https://github.com/handlebars-lang/handlebars.js.git -b 4.x .tmp/handlebars +git clone -q --depth 1 https://github.com/handlebars-lang/handlebars.js.git -b 4.x $TMP/handlebars files=(packages/kbn-handlebars/src/upstream/index.*.test.ts) @@ -16,18 +35,54 @@ do echo "Checking for changes to spec/$file.js..." set +e - diff .tmp/handlebars/spec/$file.js packages/kbn-handlebars/src/upstream/index.$file.test.ts > .tmp/$file.patch + diff -d --strip-trailing-cr $TMP/handlebars/spec/$file.js packages/kbn-handlebars/src/upstream/index.$file.test.ts > $TMP/$file.patch error=$? set -e if [ $error -gt 1 ] then - echo "Error executing diff!" + echo "The diff command encountered an unexpected error!" exit $error fi - diff -u .tmp/$file.patch packages/kbn-handlebars/.patches/$file.patch + set +e + diff -d --strip-trailing-cr $TMP/$file.patch packages/kbn-handlebars/.patches/$file.patch > $DEVNULL + error=$? + set -e + if [ $error -gt 1 ] + then + echo "The diff command encountered an unexpected error!" + exit $error + elif [ $error -gt 0 ] + then + echo + echo "The following files contain unexpected differences:" + echo + echo " Upstream: spec/$file.js" + echo " Downstream: packages/kbn-handlebars/src/upstream/index.$file.test.ts" + echo + echo "This can happen if either the upstream or the downstream version has been" + echo "updated without our patch files being kept up to date." + echo + echo "To resolve this issue, do the following:" + echo + echo " 1. Check the '4.x' branch of the upstream git repository to see if the file" + echo " has been updated. If so, please ensure that our copy of the file is kept in" + echo " sync. You can view the recent upstream commits to this file here:" + echo + echo " https://github.com/handlebars-lang/handlebars.js/commits/4.x/spec/$file.js" + echo + echo " 2. Update our patch files by running the following script. This is also" + echo " necessary even if it's only the downstream file that has been updated:" + echo + echo " ./packages/kbn-handlebars/scripts/update_test_patches.sh $file" + echo + echo " 3. Commit the changes to the updated patch file and execute this script again" + echo " until everything passes:" + echo + echo " ./packages/kbn-handlebars/scripts/check_for_test_changes.sh" + echo + exit $error + fi done echo "No changes found :)" - -rm -fr .tmp \ No newline at end of file diff --git a/packages/kbn-handlebars/scripts/update_test_patches.sh b/packages/kbn-handlebars/scripts/update_test_patches.sh index 45d87f934be81..233ca9e0f25ba 100755 --- a/packages/kbn-handlebars/scripts/update_test_patches.sh +++ b/packages/kbn-handlebars/scripts/update_test_patches.sh @@ -1,12 +1,29 @@ +#!/usr/bin/env bash + set -e -rm -fr .tmp -mkdir .tmp +TMP=.tmp-handlebars + +function cleanup { + rm -fr $TMP +} + +trap cleanup EXIT + +rm -fr $TMP +mkdir $TMP echo "Cloning handlebars repo..." -git clone -q --depth 1 https://github.com/handlebars-lang/handlebars.js.git -b 4.x .tmp/handlebars +git clone -q --depth 1 https://github.com/handlebars-lang/handlebars.js.git -b 4.x $TMP/handlebars -files=(packages/kbn-handlebars/src/upstream/index.*.test.ts) +if [ -z "$1" ] +then + # No argument given: Update all patch files + files=(packages/kbn-handlebars/src/upstream/index.*.test.ts) +else + # Argument detected: Update only the requested patch file + files=(packages/kbn-handlebars/src/upstream/index.$1.test.ts) +fi for file in "${files[@]}" do @@ -15,10 +32,8 @@ do echo "Overwriting stored patch file for spec/$file.js..." set +e - diff .tmp/handlebars/spec/$file.js packages/kbn-handlebars/src/upstream/index.$file.test.ts > packages/kbn-handlebars/.patches/$file.patch + diff -d --strip-trailing-cr $TMP/handlebars/spec/$file.js packages/kbn-handlebars/src/upstream/index.$file.test.ts > packages/kbn-handlebars/.patches/$file.patch set -e done echo "All patches updated :)" - -rm -fr .tmp \ No newline at end of file diff --git a/packages/kbn-monaco/BUILD.bazel b/packages/kbn-monaco/BUILD.bazel index 5648c71f6a281..541ba68265771 100644 --- a/packages/kbn-monaco/BUILD.bazel +++ b/packages/kbn-monaco/BUILD.bazel @@ -42,6 +42,7 @@ NPM_MODULE_EXTRA_FILES = [ RUNTIME_DEPS = [ "//packages/kbn-babel-preset", "//packages/kbn-i18n", + "//packages/kbn-ui-theme", "@npm//@babel/runtime", "@npm//antlr4ts", "@npm//babel-loader", @@ -52,6 +53,7 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-i18n:npm_module_types", + "//packages/kbn-ui-theme:npm_module_types", "@npm//antlr4ts", "@npm//monaco-editor", "@npm//rxjs", diff --git a/packages/kbn-monaco/index.ts b/packages/kbn-monaco/index.ts index dcf20faa20ac7..a2c4d18b57cf1 100644 --- a/packages/kbn-monaco/index.ts +++ b/packages/kbn-monaco/index.ts @@ -11,7 +11,9 @@ import './src/register_globals'; export { monaco } from './src/monaco_imports'; export { XJsonLang } from './src/xjson'; -export { EsqlLang } from './src/esql'; +export { SQLLang } from './src/sql'; +export { ESQL_LANG_ID, ESQL_THEME_ID } from './src/esql'; + export * from './src/painless'; /* eslint-disable-next-line @kbn/eslint/module_migration */ import * as BarePluginApi from 'monaco-editor/esm/vs/editor/editor.api'; diff --git a/packages/kbn-monaco/package.json b/packages/kbn-monaco/package.json index 71c9cbb7fb62d..05717e4922a6e 100644 --- a/packages/kbn-monaco/package.json +++ b/packages/kbn-monaco/package.json @@ -6,7 +6,10 @@ "main": "target_node/index.js", "license": "SSPL-1.0 OR Elastic License 2.0", "scripts": { - "build:antlr4ts": "../../node_modules/antlr4ts-cli/antlr4ts ./src/painless/antlr/painless_lexer.g4 ./src/painless/antlr/painless_parser.g4 && node ./scripts/fix_generated_antlr.js" + "build:antlr4ts:painless": "../../node_modules/antlr4ts-cli/antlr4ts ./src/painless/antlr/painless_lexer.g4 ./src/painless/antlr/painless_parser.g4 && node ./scripts/fix_generated_antlr.js painless", + "build:antlr4ts:esql": "../../node_modules/antlr4ts-cli/antlr4ts src/esql/antlr/esql_lexer.g4 src/esql/antlr/esql_parser.g4 && node ./scripts/fix_generated_antlr.js esql", + "build:antlr4ts": "npm run build:antlr4ts:painless && npm run build:antlr4ts:esql" + }, "types": "./target_types/index.d.ts" } diff --git a/packages/kbn-monaco/scripts/fix_generated_antlr.js b/packages/kbn-monaco/scripts/fix_generated_antlr.js index 6c6e0245f8fe0..bdb246703d04b 100644 --- a/packages/kbn-monaco/scripts/fix_generated_antlr.js +++ b/packages/kbn-monaco/scripts/fix_generated_antlr.js @@ -9,47 +9,60 @@ const { join } = require('path'); const { readdirSync, readFileSync, writeFileSync, renameSync } = require('fs'); const ora = require('ora'); +const log = ora('Updating generated antlr grammar').start(); -const generatedAntlrFolder = join(__dirname, '..', 'src', 'painless', 'antlr'); +const SUPPORTED_FOLDERS = ['painless', 'esql']; -const generatedAntlrFolderContents = readdirSync(generatedAntlrFolder); +function execute(folder) { + const generatedAntlrFolder = join(__dirname, '..', 'src', folder, 'antlr'); -const log = ora('Updating generated antlr grammar').start(); + const generatedAntlrFolderContents = readdirSync(generatedAntlrFolder); -// The generated TS produces some TS linting errors -// This script adds a //@ts-nocheck comment at the top of each generated file -// so that the errors can be ignored for now -generatedAntlrFolderContents - .filter((file) => { - const fileExtension = file.split('.')[1]; - return fileExtension.includes('ts'); - }) - .forEach((file) => { - try { - const fileContentRows = readFileSync(join(generatedAntlrFolder, file), 'utf8') - .toString() - .split('\n'); - - fileContentRows.unshift('// @ts-nocheck'); - - const filePath = join(generatedAntlrFolder, file); - const fileContent = fileContentRows.join('\n'); - - writeFileSync(filePath, fileContent, { encoding: 'utf8' }); - } catch (err) { - return log.fail(err.message); - } - }); - -// Rename generated parserListener file to snakecase to satisfy file casing check -// There doesn't appear to be a way to fix this OOTB with antlr4ts-cli -try { - renameSync( - join(generatedAntlrFolder, 'painless_parserListener.ts'), - join(generatedAntlrFolder, 'painless_parser_listener.ts') - ); -} catch (err) { - log.warn(`Unable to rename parserListener file to snakecase: ${err.message}`); + // The generated TS produces some TS linting errors + // This script adds a //@ts-nocheck comment at the top of each generated file + // so that the errors can be ignored for now + generatedAntlrFolderContents + .filter((file) => { + const fileExtension = file.split('.')[1]; + return fileExtension.includes('ts'); + }) + .forEach((file) => { + try { + const fileContentRows = readFileSync(join(generatedAntlrFolder, file), 'utf8') + .toString() + .split('\n'); + + fileContentRows.unshift('// @ts-nocheck'); + + const filePath = join(generatedAntlrFolder, file); + const fileContent = fileContentRows.join('\n'); + + writeFileSync(filePath, fileContent, { encoding: 'utf8' }); + } catch (err) { + return log.fail(err.message); + } + }); + + // Rename generated parserListener file to snakecase to satisfy file casing check + // There doesn't appear to be a way to fix this OOTB with antlr4ts-cli + try { + renameSync( + join(generatedAntlrFolder, `${folder}_parserListener.ts`), + join(generatedAntlrFolder, `${folder}_parser_listener.ts`) + ); + } catch (err) { + log.warn(`Unable to rename parserListener file to snake-case: ${err.message}`); + } + + log.succeed('Updated generated antlr grammar successfully'); } -log.succeed('Updated generated antlr grammar successfully'); +const [folder] = process.argv.slice(2); + +if (!SUPPORTED_FOLDERS.includes(folder)) { + log.warn( + `Target folder should be one of: ${SUPPORTED_FOLDERS.join(' ')}. Got: ${folder ?? '(empty)'}` + ); +} else { + execute(folder); +} diff --git a/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts b/packages/kbn-monaco/src/common/diagnostics_adapter.test.ts similarity index 96% rename from packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts rename to packages/kbn-monaco/src/common/diagnostics_adapter.test.ts index e4762957ca95a..f25aac31bb0bb 100644 --- a/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts +++ b/packages/kbn-monaco/src/common/diagnostics_adapter.test.ts @@ -12,7 +12,6 @@ import { Subscription } from 'rxjs'; import { MockIModel } from '../__jest__/types'; import { LangValidation } from '../types'; import { monaco } from '../monaco_imports'; -import { ID } from './constants'; import { DiagnosticsAdapter } from './diagnostics_adapter'; @@ -24,10 +23,12 @@ const getMockWorker = async () => { } as any; }; +const ID = 'painless'; + const flushPromises = () => new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); -describe('Painless DiagnosticAdapter', () => { +describe('DiagnosticAdapter', () => { let diagnosticAdapter: DiagnosticsAdapter; let subscription: Subscription; let model: MockIModel; @@ -43,7 +44,7 @@ describe('Painless DiagnosticAdapter', () => { beforeEach(async () => { model = monaco.editor.createModel(ID) as unknown as MockIModel; - diagnosticAdapter = new DiagnosticsAdapter(getMockWorker); + diagnosticAdapter = new DiagnosticsAdapter(ID, getMockWorker); // validate() has a promise we need to wait for // --> await worker.getSyntaxErrors() diff --git a/packages/kbn-monaco/src/painless/diagnostics_adapter.ts b/packages/kbn-monaco/src/common/diagnostics_adapter.ts similarity index 81% rename from packages/kbn-monaco/src/painless/diagnostics_adapter.ts rename to packages/kbn-monaco/src/common/diagnostics_adapter.ts index a113adb74f22d..e0169f49e7d0d 100644 --- a/packages/kbn-monaco/src/painless/diagnostics_adapter.ts +++ b/packages/kbn-monaco/src/common/diagnostics_adapter.ts @@ -9,18 +9,17 @@ import { BehaviorSubject } from 'rxjs'; import { monaco } from '../monaco_imports'; -import { SyntaxErrors, LangValidation } from '../types'; -import { ID } from './constants'; -import { WorkerAccessor } from './language'; -import { PainlessError } from './worker'; +import type { SyntaxErrors, LangValidation, EditorError, BaseWorkerDefinition } from '../types'; -const toDiagnostics = (error: PainlessError): monaco.editor.IMarkerData => { +const toDiagnostics = (error: EditorError): monaco.editor.IMarkerData => { return { ...error, severity: monaco.MarkerSeverity.Error, }; }; +export type WorkerAccessor = (...uris: monaco.Uri[]) => Promise; + export class DiagnosticsAdapter { private errors: SyntaxErrors = {}; private validation = new BehaviorSubject({ @@ -33,14 +32,14 @@ export class DiagnosticsAdapter { public validation$ = this.validation.asObservable(); - constructor(private worker: WorkerAccessor) { + constructor(private langId: string, private worker: WorkerAccessor) { const onModelAdd = (model: monaco.editor.IModel): void => { let handle: any; - if (model.getModeId() === ID) { + if (model.getModeId() === this.langId) { model.onDidChangeContent(() => { // Do not validate if the language ID has changed - if (model.getModeId() !== ID) { + if (model.getModeId() !== this.langId) { return; } @@ -54,7 +53,7 @@ export class DiagnosticsAdapter { isValidating: false, errors: [], }); - return monaco.editor.setModelMarkers(model, ID, []); + return monaco.editor.setModelMarkers(model, this.langId, []); } this.validation.next({ @@ -70,8 +69,8 @@ export class DiagnosticsAdapter { model.onDidChangeLanguage(({ newLanguage }) => { // Reset the model markers if the language ID has changed and is no longer "painless" // Otherwise, re-validate - if (newLanguage !== ID) { - return monaco.editor.setModelMarkers(model, ID, []); + if (newLanguage !== this.langId) { + return monaco.editor.setModelMarkers(model, this.langId, []); } else { this.validate(model.uri, ++this.validateIdx); } @@ -107,7 +106,7 @@ export class DiagnosticsAdapter { [model!.id]: errorMarkers, }; // Set the error markers and underline them with "Error" severity - monaco.editor.setModelMarkers(model!, ID, errorMarkers.map(toDiagnostics)); + monaco.editor.setModelMarkers(model!, this.langId, errorMarkers.map(toDiagnostics)); } const isValid = errorMarkers === undefined || errorMarkers.length === 0; diff --git a/packages/kbn-monaco/src/painless/worker/lib/error_listener.ts b/packages/kbn-monaco/src/common/error_listener.ts similarity index 76% rename from packages/kbn-monaco/src/painless/worker/lib/error_listener.ts rename to packages/kbn-monaco/src/common/error_listener.ts index bfbdb96c6c3e0..bc7b56a016460 100644 --- a/packages/kbn-monaco/src/painless/worker/lib/error_listener.ts +++ b/packages/kbn-monaco/src/common/error_listener.ts @@ -7,17 +7,10 @@ */ import { ANTLRErrorListener, RecognitionException, Recognizer } from 'antlr4ts'; +import type { EditorError } from '../types'; -export interface PainlessError { - startLineNumber: number; - startColumn: number; - endLineNumber: number; - endColumn: number; - message: string; -} - -export class PainlessErrorListener implements ANTLRErrorListener { - private errors: PainlessError[] = []; +export class ANTLREErrorListener implements ANTLRErrorListener { + private errors: EditorError[] = []; syntaxError( recognizer: Recognizer, @@ -42,7 +35,7 @@ export class PainlessErrorListener implements ANTLRErrorListener { }); } - getErrors(): PainlessError[] { + getErrors(): EditorError[] { return this.errors; } } diff --git a/packages/kbn-monaco/src/painless/lib/worker_proxy.ts b/packages/kbn-monaco/src/common/worker_proxy.ts similarity index 61% rename from packages/kbn-monaco/src/painless/lib/worker_proxy.ts rename to packages/kbn-monaco/src/common/worker_proxy.ts index e231692402544..2f07e6b8028f5 100644 --- a/packages/kbn-monaco/src/painless/lib/worker_proxy.ts +++ b/packages/kbn-monaco/src/common/worker_proxy.ts @@ -6,12 +6,11 @@ * Side Public License, v 1. */ -import { monaco } from '../../monaco_imports'; -import { PainlessWorker } from '../worker'; -import { ID } from '../constants'; +import { monaco } from '../monaco_imports'; +import type { BaseWorkerDefinition } from '../types'; -export class WorkerProxyService { - private worker: monaco.editor.MonacoWebWorker | undefined; +export class WorkerProxyService { + private worker: monaco.editor.MonacoWebWorker | undefined; public async getWorker(resources: monaco.Uri[]) { if (!this.worker) { @@ -19,12 +18,11 @@ export class WorkerProxyService { } await this.worker.withSyncedResources(resources); - const proxy = await this.worker.getProxy(); - return proxy; + return await this.worker.getProxy(); } - public setup() { - this.worker = monaco.editor.createWebWorker({ label: ID, moduleId: '' }); + public setup(langId: string) { + this.worker = monaco.editor.createWebWorker({ label: langId, moduleId: '' }); } public stop() { diff --git a/packages/kbn-monaco/src/esql/antlr/esql_lexer.g4 b/packages/kbn-monaco/src/esql/antlr/esql_lexer.g4 new file mode 100644 index 0000000000000..dd608ac7771e8 --- /dev/null +++ b/packages/kbn-monaco/src/esql/antlr/esql_lexer.g4 @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +lexer grammar esql_lexer; + +EVAL : 'eval' -> pushMode(EXPRESSION); +EXPLAIN : 'explain' -> pushMode(EXPRESSION); +FROM : 'from' -> pushMode(SOURCE_IDENTIFIERS); +ROW : 'row' -> pushMode(EXPRESSION); +STATS : 'stats' -> pushMode(EXPRESSION); +WHERE : 'where' -> pushMode(EXPRESSION); +SORT : 'sort' -> pushMode(EXPRESSION); +LIMIT : 'limit' -> pushMode(EXPRESSION); +PROJECT : 'project' -> pushMode(SOURCE_IDENTIFIERS); + +LINE_COMMENT + : '//' ~[\r\n]* '\r'? '\n'? -> channel(HIDDEN) + ; + +MULTILINE_COMMENT + : '/*' (MULTILINE_COMMENT|.)*? '*/' -> channel(HIDDEN) + ; + +WS + : [ \r\n\t]+ -> channel(HIDDEN) + ; + + +mode EXPRESSION; + +PIPE : '|' -> popMode; + +fragment DIGIT + : [0-9] + ; + +fragment LETTER + : [A-Za-z] + ; + +fragment ESCAPE_SEQUENCE + : '\\' [tnr"\\] + ; + +fragment UNESCAPED_CHARS + : ~[\r\n"\\] + ; + +fragment EXPONENT + : [Ee] [+-]? DIGIT+ + ; + +STRING + : '"' (ESCAPE_SEQUENCE | UNESCAPED_CHARS)* '"' + | '"""' (~[\r\n])*? '"""' '"'? '"'? + ; + +INTEGER_LITERAL + : DIGIT+ + ; + +DECIMAL_LITERAL + : DIGIT+ DOT DIGIT* + | DOT DIGIT+ + | DIGIT+ (DOT DIGIT*)? EXPONENT + | DOT DIGIT+ EXPONENT + ; + +BY : 'by'; + +AND : 'and'; +ASC : 'asc'; +ASSIGN : '='; +COMMA : ','; +DESC : 'desc'; +DOT : '.'; +FALSE : 'false'; +FIRST : 'first'; +LAST : 'last'; +LP : '('; +OPENING_BRACKET : '[' -> pushMode(DEFAULT_MODE); +CLOSING_BRACKET : ']' -> popMode, popMode; // pop twice, once to clear mode of current cmd and once to exit DEFAULT_MODE +NOT : 'not'; +NULL : 'null'; +NULLS : 'nulls'; +OR : 'or'; +RP : ')'; +TRUE : 'true'; + +EQ : '=='; +NEQ : '!='; +LT : '<'; +LTE : '<='; +GT : '>'; +GTE : '>='; + +PLUS : '+'; +MINUS : '-'; +ASTERISK : '*'; +SLASH : '/'; +PERCENT : '%'; + +ROUND_FUNCTION_MATH : 'round'; +AVG_FUNCTION_MATH : 'avg'; +SUM_FUNCTION_MATH : 'sum'; +MIN_FUNCTION_MATH : 'min'; +MAX_FUNCTION_MATH : 'max'; + + +UNQUOTED_IDENTIFIER + : (LETTER | '_') (LETTER | DIGIT | '_')* + ; + +QUOTED_IDENTIFIER + : '`' ( ~'`' | '``' )* '`' + ; + +EXPR_LINE_COMMENT + : LINE_COMMENT -> channel(HIDDEN) + ; + +EXPR_MULTILINE_COMMENT + : MULTILINE_COMMENT -> channel(HIDDEN) + ; + +EXPR_WS + : WS -> channel(HIDDEN) + ; + + +mode SOURCE_IDENTIFIERS; + +SRC_PIPE : '|' -> type(PIPE), popMode; +SRC_CLOSING_BRACKET : ']' -> popMode, popMode, type(CLOSING_BRACKET); +SRC_COMMA : ',' -> type(COMMA); +SRC_ASSIGN : '=' -> type(ASSIGN); + +SRC_UNQUOTED_IDENTIFIER + : SRC_UNQUOTED_IDENTIFIER_PART+ + ; + +fragment SRC_UNQUOTED_IDENTIFIER_PART + : ~[=`|,[\]/ \t\r\n]+ + | '/' ~[*/] // allow single / but not followed by another / or * which would start a comment + ; + +SRC_QUOTED_IDENTIFIER + : QUOTED_IDENTIFIER + ; + +SRC_LINE_COMMENT + : LINE_COMMENT -> channel(HIDDEN) + ; + +SRC_MULTILINE_COMMENT + : MULTILINE_COMMENT -> channel(HIDDEN) + ; + +SRC_WS + : WS -> channel(HIDDEN) + ; + +UNKNOWN_CMD : ~[ \r\n\t[\]/]+ -> pushMode(EXPRESSION); diff --git a/packages/kbn-monaco/src/esql/antlr/esql_lexer.interp b/packages/kbn-monaco/src/esql/antlr/esql_lexer.interp new file mode 100644 index 0000000000000..48aad053f07ec --- /dev/null +++ b/packages/kbn-monaco/src/esql/antlr/esql_lexer.interp @@ -0,0 +1,215 @@ +token literal names: +null +'eval' +'explain' +'from' +'row' +'stats' +'where' +'sort' +'limit' +'project' +null +null +null +null +null +null +null +'by' +'and' +'asc' +null +null +'desc' +'.' +'false' +'first' +'last' +'(' +'[' +']' +'not' +'null' +'nulls' +'or' +')' +'true' +'==' +'!=' +'<' +'<=' +'>' +'>=' +'+' +'-' +'*' +'/' +'%' +'round' +'avg' +'sum' +'min' +'max' +null +null +null +null +null +null +null +null +null +null +null + +token symbolic names: +null +EVAL +EXPLAIN +FROM +ROW +STATS +WHERE +SORT +LIMIT +PROJECT +LINE_COMMENT +MULTILINE_COMMENT +WS +PIPE +STRING +INTEGER_LITERAL +DECIMAL_LITERAL +BY +AND +ASC +ASSIGN +COMMA +DESC +DOT +FALSE +FIRST +LAST +LP +OPENING_BRACKET +CLOSING_BRACKET +NOT +NULL +NULLS +OR +RP +TRUE +EQ +NEQ +LT +LTE +GT +GTE +PLUS +MINUS +ASTERISK +SLASH +PERCENT +ROUND_FUNCTION_MATH +AVG_FUNCTION_MATH +SUM_FUNCTION_MATH +MIN_FUNCTION_MATH +MAX_FUNCTION_MATH +UNQUOTED_IDENTIFIER +QUOTED_IDENTIFIER +EXPR_LINE_COMMENT +EXPR_MULTILINE_COMMENT +EXPR_WS +SRC_UNQUOTED_IDENTIFIER +SRC_QUOTED_IDENTIFIER +SRC_LINE_COMMENT +SRC_MULTILINE_COMMENT +SRC_WS +UNKNOWN_CMD + +rule names: +EVAL +EXPLAIN +FROM +ROW +STATS +WHERE +SORT +LIMIT +PROJECT +LINE_COMMENT +MULTILINE_COMMENT +WS +PIPE +DIGIT +LETTER +ESCAPE_SEQUENCE +UNESCAPED_CHARS +EXPONENT +STRING +INTEGER_LITERAL +DECIMAL_LITERAL +BY +AND +ASC +ASSIGN +COMMA +DESC +DOT +FALSE +FIRST +LAST +LP +OPENING_BRACKET +CLOSING_BRACKET +NOT +NULL +NULLS +OR +RP +TRUE +EQ +NEQ +LT +LTE +GT +GTE +PLUS +MINUS +ASTERISK +SLASH +PERCENT +ROUND_FUNCTION_MATH +AVG_FUNCTION_MATH +SUM_FUNCTION_MATH +MIN_FUNCTION_MATH +MAX_FUNCTION_MATH +UNQUOTED_IDENTIFIER +QUOTED_IDENTIFIER +EXPR_LINE_COMMENT +EXPR_MULTILINE_COMMENT +EXPR_WS +SRC_PIPE +SRC_CLOSING_BRACKET +SRC_COMMA +SRC_ASSIGN +SRC_UNQUOTED_IDENTIFIER +SRC_UNQUOTED_IDENTIFIER_PART +SRC_QUOTED_IDENTIFIER +SRC_LINE_COMMENT +SRC_MULTILINE_COMMENT +SRC_WS +UNKNOWN_CMD + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE +EXPRESSION +SOURCE_IDENTIFIERS + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 2, 64, 573, 8, 1, 8, 1, 8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 4, 67, 9, 67, 4, 68, 9, 68, 4, 69, 9, 69, 4, 70, 9, 70, 4, 71, 9, 71, 4, 72, 9, 72, 4, 73, 9, 73, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 11, 7, 11, 225, 10, 11, 12, 11, 14, 11, 228, 11, 11, 3, 11, 5, 11, 231, 10, 11, 3, 11, 5, 11, 234, 10, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 7, 12, 243, 10, 12, 12, 12, 14, 12, 246, 11, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 13, 6, 13, 254, 10, 13, 13, 13, 14, 13, 255, 3, 13, 3, 13, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 5, 19, 275, 10, 19, 3, 19, 6, 19, 278, 10, 19, 13, 19, 14, 19, 279, 3, 20, 3, 20, 3, 20, 7, 20, 285, 10, 20, 12, 20, 14, 20, 288, 11, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 7, 20, 296, 10, 20, 12, 20, 14, 20, 299, 11, 20, 3, 20, 3, 20, 3, 20, 3, 20, 3, 20, 5, 20, 306, 10, 20, 3, 20, 5, 20, 309, 10, 20, 5, 20, 311, 10, 20, 3, 21, 6, 21, 314, 10, 21, 13, 21, 14, 21, 315, 3, 22, 6, 22, 319, 10, 22, 13, 22, 14, 22, 320, 3, 22, 3, 22, 7, 22, 325, 10, 22, 12, 22, 14, 22, 328, 11, 22, 3, 22, 3, 22, 6, 22, 332, 10, 22, 13, 22, 14, 22, 333, 3, 22, 6, 22, 337, 10, 22, 13, 22, 14, 22, 338, 3, 22, 3, 22, 7, 22, 343, 10, 22, 12, 22, 14, 22, 346, 11, 22, 5, 22, 348, 10, 22, 3, 22, 3, 22, 3, 22, 3, 22, 6, 22, 354, 10, 22, 13, 22, 14, 22, 355, 3, 22, 3, 22, 5, 22, 360, 10, 22, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3, 32, 3, 32, 3, 32, 3, 33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3, 35, 3, 35, 3, 36, 3, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 41, 3, 41, 3, 41, 3, 41, 3, 41, 3, 42, 3, 42, 3, 42, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 45, 3, 45, 3, 45, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 48, 3, 48, 3, 49, 3, 49, 3, 50, 3, 50, 3, 51, 3, 51, 3, 52, 3, 52, 3, 53, 3, 53, 3, 53, 3, 53, 3, 53, 3, 53, 3, 54, 3, 54, 3, 54, 3, 54, 3, 55, 3, 55, 3, 55, 3, 55, 3, 56, 3, 56, 3, 56, 3, 56, 3, 57, 3, 57, 3, 57, 3, 57, 3, 58, 3, 58, 5, 58, 487, 10, 58, 3, 58, 3, 58, 3, 58, 7, 58, 492, 10, 58, 12, 58, 14, 58, 495, 11, 58, 3, 59, 3, 59, 3, 59, 3, 59, 7, 59, 501, 10, 59, 12, 59, 14, 59, 504, 11, 59, 3, 59, 3, 59, 3, 60, 3, 60, 3, 60, 3, 60, 3, 61, 3, 61, 3, 61, 3, 61, 3, 62, 3, 62, 3, 62, 3, 62, 3, 63, 3, 63, 3, 63, 3, 63, 3, 63, 3, 64, 3, 64, 3, 64, 3, 64, 3, 64, 3, 64, 3, 65, 3, 65, 3, 65, 3, 65, 3, 66, 3, 66, 3, 66, 3, 66, 3, 67, 6, 67, 540, 10, 67, 13, 67, 14, 67, 541, 3, 68, 6, 68, 545, 10, 68, 13, 68, 14, 68, 546, 3, 68, 3, 68, 5, 68, 551, 10, 68, 3, 69, 3, 69, 3, 70, 3, 70, 3, 70, 3, 70, 3, 71, 3, 71, 3, 71, 3, 71, 3, 72, 3, 72, 3, 72, 3, 72, 3, 73, 6, 73, 568, 10, 73, 13, 73, 14, 73, 569, 3, 73, 3, 73, 4, 244, 297, 2, 2, 74, 5, 2, 3, 7, 2, 4, 9, 2, 5, 11, 2, 6, 13, 2, 7, 15, 2, 8, 17, 2, 9, 19, 2, 10, 21, 2, 11, 23, 2, 12, 25, 2, 13, 27, 2, 14, 29, 2, 15, 31, 2, 2, 33, 2, 2, 35, 2, 2, 37, 2, 2, 39, 2, 2, 41, 2, 16, 43, 2, 17, 45, 2, 18, 47, 2, 19, 49, 2, 20, 51, 2, 21, 53, 2, 22, 55, 2, 23, 57, 2, 24, 59, 2, 25, 61, 2, 26, 63, 2, 27, 65, 2, 28, 67, 2, 29, 69, 2, 30, 71, 2, 31, 73, 2, 32, 75, 2, 33, 77, 2, 34, 79, 2, 35, 81, 2, 36, 83, 2, 37, 85, 2, 38, 87, 2, 39, 89, 2, 40, 91, 2, 41, 93, 2, 42, 95, 2, 43, 97, 2, 44, 99, 2, 45, 101, 2, 46, 103, 2, 47, 105, 2, 48, 107, 2, 49, 109, 2, 50, 111, 2, 51, 113, 2, 52, 115, 2, 53, 117, 2, 54, 119, 2, 55, 121, 2, 56, 123, 2, 57, 125, 2, 58, 127, 2, 2, 129, 2, 2, 131, 2, 2, 133, 2, 2, 135, 2, 59, 137, 2, 2, 139, 2, 60, 141, 2, 61, 143, 2, 62, 145, 2, 63, 147, 2, 64, 5, 2, 3, 4, 14, 4, 2, 12, 12, 15, 15, 5, 2, 11, 12, 15, 15, 34, 34, 3, 2, 50, 59, 4, 2, 67, 92, 99, 124, 7, 2, 36, 36, 94, 94, 112, 112, 116, 116, 118, 118, 6, 2, 12, 12, 15, 15, 36, 36, 94, 94, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47, 3, 2, 98, 98, 12, 2, 11, 12, 15, 15, 34, 34, 46, 46, 49, 49, 63, 63, 93, 93, 95, 95, 98, 98, 126, 126, 4, 2, 44, 44, 49, 49, 8, 2, 11, 12, 15, 15, 34, 34, 49, 49, 93, 93, 95, 95, 2, 599, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 3, 29, 3, 2, 2, 2, 3, 41, 3, 2, 2, 2, 3, 43, 3, 2, 2, 2, 3, 45, 3, 2, 2, 2, 3, 47, 3, 2, 2, 2, 3, 49, 3, 2, 2, 2, 3, 51, 3, 2, 2, 2, 3, 53, 3, 2, 2, 2, 3, 55, 3, 2, 2, 2, 3, 57, 3, 2, 2, 2, 3, 59, 3, 2, 2, 2, 3, 61, 3, 2, 2, 2, 3, 63, 3, 2, 2, 2, 3, 65, 3, 2, 2, 2, 3, 67, 3, 2, 2, 2, 3, 69, 3, 2, 2, 2, 3, 71, 3, 2, 2, 2, 3, 73, 3, 2, 2, 2, 3, 75, 3, 2, 2, 2, 3, 77, 3, 2, 2, 2, 3, 79, 3, 2, 2, 2, 3, 81, 3, 2, 2, 2, 3, 83, 3, 2, 2, 2, 3, 85, 3, 2, 2, 2, 3, 87, 3, 2, 2, 2, 3, 89, 3, 2, 2, 2, 3, 91, 3, 2, 2, 2, 3, 93, 3, 2, 2, 2, 3, 95, 3, 2, 2, 2, 3, 97, 3, 2, 2, 2, 3, 99, 3, 2, 2, 2, 3, 101, 3, 2, 2, 2, 3, 103, 3, 2, 2, 2, 3, 105, 3, 2, 2, 2, 3, 107, 3, 2, 2, 2, 3, 109, 3, 2, 2, 2, 3, 111, 3, 2, 2, 2, 3, 113, 3, 2, 2, 2, 3, 115, 3, 2, 2, 2, 3, 117, 3, 2, 2, 2, 3, 119, 3, 2, 2, 2, 3, 121, 3, 2, 2, 2, 3, 123, 3, 2, 2, 2, 3, 125, 3, 2, 2, 2, 4, 127, 3, 2, 2, 2, 4, 129, 3, 2, 2, 2, 4, 131, 3, 2, 2, 2, 4, 133, 3, 2, 2, 2, 4, 135, 3, 2, 2, 2, 4, 139, 3, 2, 2, 2, 4, 141, 3, 2, 2, 2, 4, 143, 3, 2, 2, 2, 4, 145, 3, 2, 2, 2, 4, 147, 3, 2, 2, 2, 5, 149, 3, 2, 2, 2, 7, 156, 3, 2, 2, 2, 9, 166, 3, 2, 2, 2, 11, 173, 3, 2, 2, 2, 13, 179, 3, 2, 2, 2, 15, 187, 3, 2, 2, 2, 17, 195, 3, 2, 2, 2, 19, 202, 3, 2, 2, 2, 21, 210, 3, 2, 2, 2, 23, 220, 3, 2, 2, 2, 25, 237, 3, 2, 2, 2, 27, 253, 3, 2, 2, 2, 29, 259, 3, 2, 2, 2, 31, 263, 3, 2, 2, 2, 33, 265, 3, 2, 2, 2, 35, 267, 3, 2, 2, 2, 37, 270, 3, 2, 2, 2, 39, 272, 3, 2, 2, 2, 41, 310, 3, 2, 2, 2, 43, 313, 3, 2, 2, 2, 45, 359, 3, 2, 2, 2, 47, 361, 3, 2, 2, 2, 49, 364, 3, 2, 2, 2, 51, 368, 3, 2, 2, 2, 53, 372, 3, 2, 2, 2, 55, 374, 3, 2, 2, 2, 57, 376, 3, 2, 2, 2, 59, 381, 3, 2, 2, 2, 61, 383, 3, 2, 2, 2, 63, 389, 3, 2, 2, 2, 65, 395, 3, 2, 2, 2, 67, 400, 3, 2, 2, 2, 69, 402, 3, 2, 2, 2, 71, 406, 3, 2, 2, 2, 73, 411, 3, 2, 2, 2, 75, 415, 3, 2, 2, 2, 77, 420, 3, 2, 2, 2, 79, 426, 3, 2, 2, 2, 81, 429, 3, 2, 2, 2, 83, 431, 3, 2, 2, 2, 85, 436, 3, 2, 2, 2, 87, 439, 3, 2, 2, 2, 89, 442, 3, 2, 2, 2, 91, 444, 3, 2, 2, 2, 93, 447, 3, 2, 2, 2, 95, 449, 3, 2, 2, 2, 97, 452, 3, 2, 2, 2, 99, 454, 3, 2, 2, 2, 101, 456, 3, 2, 2, 2, 103, 458, 3, 2, 2, 2, 105, 460, 3, 2, 2, 2, 107, 462, 3, 2, 2, 2, 109, 468, 3, 2, 2, 2, 111, 472, 3, 2, 2, 2, 113, 476, 3, 2, 2, 2, 115, 480, 3, 2, 2, 2, 117, 486, 3, 2, 2, 2, 119, 496, 3, 2, 2, 2, 121, 507, 3, 2, 2, 2, 123, 511, 3, 2, 2, 2, 125, 515, 3, 2, 2, 2, 127, 519, 3, 2, 2, 2, 129, 524, 3, 2, 2, 2, 131, 530, 3, 2, 2, 2, 133, 534, 3, 2, 2, 2, 135, 539, 3, 2, 2, 2, 137, 550, 3, 2, 2, 2, 139, 552, 3, 2, 2, 2, 141, 554, 3, 2, 2, 2, 143, 558, 3, 2, 2, 2, 145, 562, 3, 2, 2, 2, 147, 567, 3, 2, 2, 2, 149, 150, 7, 103, 2, 2, 150, 151, 7, 120, 2, 2, 151, 152, 7, 99, 2, 2, 152, 153, 7, 110, 2, 2, 153, 154, 3, 2, 2, 2, 154, 155, 8, 2, 2, 2, 155, 6, 3, 2, 2, 2, 156, 157, 7, 103, 2, 2, 157, 158, 7, 122, 2, 2, 158, 159, 7, 114, 2, 2, 159, 160, 7, 110, 2, 2, 160, 161, 7, 99, 2, 2, 161, 162, 7, 107, 2, 2, 162, 163, 7, 112, 2, 2, 163, 164, 3, 2, 2, 2, 164, 165, 8, 3, 2, 2, 165, 8, 3, 2, 2, 2, 166, 167, 7, 104, 2, 2, 167, 168, 7, 116, 2, 2, 168, 169, 7, 113, 2, 2, 169, 170, 7, 111, 2, 2, 170, 171, 3, 2, 2, 2, 171, 172, 8, 4, 3, 2, 172, 10, 3, 2, 2, 2, 173, 174, 7, 116, 2, 2, 174, 175, 7, 113, 2, 2, 175, 176, 7, 121, 2, 2, 176, 177, 3, 2, 2, 2, 177, 178, 8, 5, 2, 2, 178, 12, 3, 2, 2, 2, 179, 180, 7, 117, 2, 2, 180, 181, 7, 118, 2, 2, 181, 182, 7, 99, 2, 2, 182, 183, 7, 118, 2, 2, 183, 184, 7, 117, 2, 2, 184, 185, 3, 2, 2, 2, 185, 186, 8, 6, 2, 2, 186, 14, 3, 2, 2, 2, 187, 188, 7, 121, 2, 2, 188, 189, 7, 106, 2, 2, 189, 190, 7, 103, 2, 2, 190, 191, 7, 116, 2, 2, 191, 192, 7, 103, 2, 2, 192, 193, 3, 2, 2, 2, 193, 194, 8, 7, 2, 2, 194, 16, 3, 2, 2, 2, 195, 196, 7, 117, 2, 2, 196, 197, 7, 113, 2, 2, 197, 198, 7, 116, 2, 2, 198, 199, 7, 118, 2, 2, 199, 200, 3, 2, 2, 2, 200, 201, 8, 8, 2, 2, 201, 18, 3, 2, 2, 2, 202, 203, 7, 110, 2, 2, 203, 204, 7, 107, 2, 2, 204, 205, 7, 111, 2, 2, 205, 206, 7, 107, 2, 2, 206, 207, 7, 118, 2, 2, 207, 208, 3, 2, 2, 2, 208, 209, 8, 9, 2, 2, 209, 20, 3, 2, 2, 2, 210, 211, 7, 114, 2, 2, 211, 212, 7, 116, 2, 2, 212, 213, 7, 113, 2, 2, 213, 214, 7, 108, 2, 2, 214, 215, 7, 103, 2, 2, 215, 216, 7, 101, 2, 2, 216, 217, 7, 118, 2, 2, 217, 218, 3, 2, 2, 2, 218, 219, 8, 10, 3, 2, 219, 22, 3, 2, 2, 2, 220, 221, 7, 49, 2, 2, 221, 222, 7, 49, 2, 2, 222, 226, 3, 2, 2, 2, 223, 225, 10, 2, 2, 2, 224, 223, 3, 2, 2, 2, 225, 228, 3, 2, 2, 2, 226, 224, 3, 2, 2, 2, 226, 227, 3, 2, 2, 2, 227, 230, 3, 2, 2, 2, 228, 226, 3, 2, 2, 2, 229, 231, 7, 15, 2, 2, 230, 229, 3, 2, 2, 2, 230, 231, 3, 2, 2, 2, 231, 233, 3, 2, 2, 2, 232, 234, 7, 12, 2, 2, 233, 232, 3, 2, 2, 2, 233, 234, 3, 2, 2, 2, 234, 235, 3, 2, 2, 2, 235, 236, 8, 11, 4, 2, 236, 24, 3, 2, 2, 2, 237, 238, 7, 49, 2, 2, 238, 239, 7, 44, 2, 2, 239, 244, 3, 2, 2, 2, 240, 243, 5, 25, 12, 2, 241, 243, 11, 2, 2, 2, 242, 240, 3, 2, 2, 2, 242, 241, 3, 2, 2, 2, 243, 246, 3, 2, 2, 2, 244, 245, 3, 2, 2, 2, 244, 242, 3, 2, 2, 2, 245, 247, 3, 2, 2, 2, 246, 244, 3, 2, 2, 2, 247, 248, 7, 44, 2, 2, 248, 249, 7, 49, 2, 2, 249, 250, 3, 2, 2, 2, 250, 251, 8, 12, 4, 2, 251, 26, 3, 2, 2, 2, 252, 254, 9, 3, 2, 2, 253, 252, 3, 2, 2, 2, 254, 255, 3, 2, 2, 2, 255, 253, 3, 2, 2, 2, 255, 256, 3, 2, 2, 2, 256, 257, 3, 2, 2, 2, 257, 258, 8, 13, 4, 2, 258, 28, 3, 2, 2, 2, 259, 260, 7, 126, 2, 2, 260, 261, 3, 2, 2, 2, 261, 262, 8, 14, 5, 2, 262, 30, 3, 2, 2, 2, 263, 264, 9, 4, 2, 2, 264, 32, 3, 2, 2, 2, 265, 266, 9, 5, 2, 2, 266, 34, 3, 2, 2, 2, 267, 268, 7, 94, 2, 2, 268, 269, 9, 6, 2, 2, 269, 36, 3, 2, 2, 2, 270, 271, 10, 7, 2, 2, 271, 38, 3, 2, 2, 2, 272, 274, 9, 8, 2, 2, 273, 275, 9, 9, 2, 2, 274, 273, 3, 2, 2, 2, 274, 275, 3, 2, 2, 2, 275, 277, 3, 2, 2, 2, 276, 278, 5, 31, 15, 2, 277, 276, 3, 2, 2, 2, 278, 279, 3, 2, 2, 2, 279, 277, 3, 2, 2, 2, 279, 280, 3, 2, 2, 2, 280, 40, 3, 2, 2, 2, 281, 286, 7, 36, 2, 2, 282, 285, 5, 35, 17, 2, 283, 285, 5, 37, 18, 2, 284, 282, 3, 2, 2, 2, 284, 283, 3, 2, 2, 2, 285, 288, 3, 2, 2, 2, 286, 284, 3, 2, 2, 2, 286, 287, 3, 2, 2, 2, 287, 289, 3, 2, 2, 2, 288, 286, 3, 2, 2, 2, 289, 311, 7, 36, 2, 2, 290, 291, 7, 36, 2, 2, 291, 292, 7, 36, 2, 2, 292, 293, 7, 36, 2, 2, 293, 297, 3, 2, 2, 2, 294, 296, 10, 2, 2, 2, 295, 294, 3, 2, 2, 2, 296, 299, 3, 2, 2, 2, 297, 298, 3, 2, 2, 2, 297, 295, 3, 2, 2, 2, 298, 300, 3, 2, 2, 2, 299, 297, 3, 2, 2, 2, 300, 301, 7, 36, 2, 2, 301, 302, 7, 36, 2, 2, 302, 303, 7, 36, 2, 2, 303, 305, 3, 2, 2, 2, 304, 306, 7, 36, 2, 2, 305, 304, 3, 2, 2, 2, 305, 306, 3, 2, 2, 2, 306, 308, 3, 2, 2, 2, 307, 309, 7, 36, 2, 2, 308, 307, 3, 2, 2, 2, 308, 309, 3, 2, 2, 2, 309, 311, 3, 2, 2, 2, 310, 281, 3, 2, 2, 2, 310, 290, 3, 2, 2, 2, 311, 42, 3, 2, 2, 2, 312, 314, 5, 31, 15, 2, 313, 312, 3, 2, 2, 2, 314, 315, 3, 2, 2, 2, 315, 313, 3, 2, 2, 2, 315, 316, 3, 2, 2, 2, 316, 44, 3, 2, 2, 2, 317, 319, 5, 31, 15, 2, 318, 317, 3, 2, 2, 2, 319, 320, 3, 2, 2, 2, 320, 318, 3, 2, 2, 2, 320, 321, 3, 2, 2, 2, 321, 322, 3, 2, 2, 2, 322, 326, 5, 59, 29, 2, 323, 325, 5, 31, 15, 2, 324, 323, 3, 2, 2, 2, 325, 328, 3, 2, 2, 2, 326, 324, 3, 2, 2, 2, 326, 327, 3, 2, 2, 2, 327, 360, 3, 2, 2, 2, 328, 326, 3, 2, 2, 2, 329, 331, 5, 59, 29, 2, 330, 332, 5, 31, 15, 2, 331, 330, 3, 2, 2, 2, 332, 333, 3, 2, 2, 2, 333, 331, 3, 2, 2, 2, 333, 334, 3, 2, 2, 2, 334, 360, 3, 2, 2, 2, 335, 337, 5, 31, 15, 2, 336, 335, 3, 2, 2, 2, 337, 338, 3, 2, 2, 2, 338, 336, 3, 2, 2, 2, 338, 339, 3, 2, 2, 2, 339, 347, 3, 2, 2, 2, 340, 344, 5, 59, 29, 2, 341, 343, 5, 31, 15, 2, 342, 341, 3, 2, 2, 2, 343, 346, 3, 2, 2, 2, 344, 342, 3, 2, 2, 2, 344, 345, 3, 2, 2, 2, 345, 348, 3, 2, 2, 2, 346, 344, 3, 2, 2, 2, 347, 340, 3, 2, 2, 2, 347, 348, 3, 2, 2, 2, 348, 349, 3, 2, 2, 2, 349, 350, 5, 39, 19, 2, 350, 360, 3, 2, 2, 2, 351, 353, 5, 59, 29, 2, 352, 354, 5, 31, 15, 2, 353, 352, 3, 2, 2, 2, 354, 355, 3, 2, 2, 2, 355, 353, 3, 2, 2, 2, 355, 356, 3, 2, 2, 2, 356, 357, 3, 2, 2, 2, 357, 358, 5, 39, 19, 2, 358, 360, 3, 2, 2, 2, 359, 318, 3, 2, 2, 2, 359, 329, 3, 2, 2, 2, 359, 336, 3, 2, 2, 2, 359, 351, 3, 2, 2, 2, 360, 46, 3, 2, 2, 2, 361, 362, 7, 100, 2, 2, 362, 363, 7, 123, 2, 2, 363, 48, 3, 2, 2, 2, 364, 365, 7, 99, 2, 2, 365, 366, 7, 112, 2, 2, 366, 367, 7, 102, 2, 2, 367, 50, 3, 2, 2, 2, 368, 369, 7, 99, 2, 2, 369, 370, 7, 117, 2, 2, 370, 371, 7, 101, 2, 2, 371, 52, 3, 2, 2, 2, 372, 373, 7, 63, 2, 2, 373, 54, 3, 2, 2, 2, 374, 375, 7, 46, 2, 2, 375, 56, 3, 2, 2, 2, 376, 377, 7, 102, 2, 2, 377, 378, 7, 103, 2, 2, 378, 379, 7, 117, 2, 2, 379, 380, 7, 101, 2, 2, 380, 58, 3, 2, 2, 2, 381, 382, 7, 48, 2, 2, 382, 60, 3, 2, 2, 2, 383, 384, 7, 104, 2, 2, 384, 385, 7, 99, 2, 2, 385, 386, 7, 110, 2, 2, 386, 387, 7, 117, 2, 2, 387, 388, 7, 103, 2, 2, 388, 62, 3, 2, 2, 2, 389, 390, 7, 104, 2, 2, 390, 391, 7, 107, 2, 2, 391, 392, 7, 116, 2, 2, 392, 393, 7, 117, 2, 2, 393, 394, 7, 118, 2, 2, 394, 64, 3, 2, 2, 2, 395, 396, 7, 110, 2, 2, 396, 397, 7, 99, 2, 2, 397, 398, 7, 117, 2, 2, 398, 399, 7, 118, 2, 2, 399, 66, 3, 2, 2, 2, 400, 401, 7, 42, 2, 2, 401, 68, 3, 2, 2, 2, 402, 403, 7, 93, 2, 2, 403, 404, 3, 2, 2, 2, 404, 405, 8, 34, 6, 2, 405, 70, 3, 2, 2, 2, 406, 407, 7, 95, 2, 2, 407, 408, 3, 2, 2, 2, 408, 409, 8, 35, 5, 2, 409, 410, 8, 35, 5, 2, 410, 72, 3, 2, 2, 2, 411, 412, 7, 112, 2, 2, 412, 413, 7, 113, 2, 2, 413, 414, 7, 118, 2, 2, 414, 74, 3, 2, 2, 2, 415, 416, 7, 112, 2, 2, 416, 417, 7, 119, 2, 2, 417, 418, 7, 110, 2, 2, 418, 419, 7, 110, 2, 2, 419, 76, 3, 2, 2, 2, 420, 421, 7, 112, 2, 2, 421, 422, 7, 119, 2, 2, 422, 423, 7, 110, 2, 2, 423, 424, 7, 110, 2, 2, 424, 425, 7, 117, 2, 2, 425, 78, 3, 2, 2, 2, 426, 427, 7, 113, 2, 2, 427, 428, 7, 116, 2, 2, 428, 80, 3, 2, 2, 2, 429, 430, 7, 43, 2, 2, 430, 82, 3, 2, 2, 2, 431, 432, 7, 118, 2, 2, 432, 433, 7, 116, 2, 2, 433, 434, 7, 119, 2, 2, 434, 435, 7, 103, 2, 2, 435, 84, 3, 2, 2, 2, 436, 437, 7, 63, 2, 2, 437, 438, 7, 63, 2, 2, 438, 86, 3, 2, 2, 2, 439, 440, 7, 35, 2, 2, 440, 441, 7, 63, 2, 2, 441, 88, 3, 2, 2, 2, 442, 443, 7, 62, 2, 2, 443, 90, 3, 2, 2, 2, 444, 445, 7, 62, 2, 2, 445, 446, 7, 63, 2, 2, 446, 92, 3, 2, 2, 2, 447, 448, 7, 64, 2, 2, 448, 94, 3, 2, 2, 2, 449, 450, 7, 64, 2, 2, 450, 451, 7, 63, 2, 2, 451, 96, 3, 2, 2, 2, 452, 453, 7, 45, 2, 2, 453, 98, 3, 2, 2, 2, 454, 455, 7, 47, 2, 2, 455, 100, 3, 2, 2, 2, 456, 457, 7, 44, 2, 2, 457, 102, 3, 2, 2, 2, 458, 459, 7, 49, 2, 2, 459, 104, 3, 2, 2, 2, 460, 461, 7, 39, 2, 2, 461, 106, 3, 2, 2, 2, 462, 463, 7, 116, 2, 2, 463, 464, 7, 113, 2, 2, 464, 465, 7, 119, 2, 2, 465, 466, 7, 112, 2, 2, 466, 467, 7, 102, 2, 2, 467, 108, 3, 2, 2, 2, 468, 469, 7, 99, 2, 2, 469, 470, 7, 120, 2, 2, 470, 471, 7, 105, 2, 2, 471, 110, 3, 2, 2, 2, 472, 473, 7, 117, 2, 2, 473, 474, 7, 119, 2, 2, 474, 475, 7, 111, 2, 2, 475, 112, 3, 2, 2, 2, 476, 477, 7, 111, 2, 2, 477, 478, 7, 107, 2, 2, 478, 479, 7, 112, 2, 2, 479, 114, 3, 2, 2, 2, 480, 481, 7, 111, 2, 2, 481, 482, 7, 99, 2, 2, 482, 483, 7, 122, 2, 2, 483, 116, 3, 2, 2, 2, 484, 487, 5, 33, 16, 2, 485, 487, 7, 97, 2, 2, 486, 484, 3, 2, 2, 2, 486, 485, 3, 2, 2, 2, 487, 493, 3, 2, 2, 2, 488, 492, 5, 33, 16, 2, 489, 492, 5, 31, 15, 2, 490, 492, 7, 97, 2, 2, 491, 488, 3, 2, 2, 2, 491, 489, 3, 2, 2, 2, 491, 490, 3, 2, 2, 2, 492, 495, 3, 2, 2, 2, 493, 491, 3, 2, 2, 2, 493, 494, 3, 2, 2, 2, 494, 118, 3, 2, 2, 2, 495, 493, 3, 2, 2, 2, 496, 502, 7, 98, 2, 2, 497, 501, 10, 10, 2, 2, 498, 499, 7, 98, 2, 2, 499, 501, 7, 98, 2, 2, 500, 497, 3, 2, 2, 2, 500, 498, 3, 2, 2, 2, 501, 504, 3, 2, 2, 2, 502, 500, 3, 2, 2, 2, 502, 503, 3, 2, 2, 2, 503, 505, 3, 2, 2, 2, 504, 502, 3, 2, 2, 2, 505, 506, 7, 98, 2, 2, 506, 120, 3, 2, 2, 2, 507, 508, 5, 23, 11, 2, 508, 509, 3, 2, 2, 2, 509, 510, 8, 60, 4, 2, 510, 122, 3, 2, 2, 2, 511, 512, 5, 25, 12, 2, 512, 513, 3, 2, 2, 2, 513, 514, 8, 61, 4, 2, 514, 124, 3, 2, 2, 2, 515, 516, 5, 27, 13, 2, 516, 517, 3, 2, 2, 2, 517, 518, 8, 62, 4, 2, 518, 126, 3, 2, 2, 2, 519, 520, 7, 126, 2, 2, 520, 521, 3, 2, 2, 2, 521, 522, 8, 63, 7, 2, 522, 523, 8, 63, 5, 2, 523, 128, 3, 2, 2, 2, 524, 525, 7, 95, 2, 2, 525, 526, 3, 2, 2, 2, 526, 527, 8, 64, 5, 2, 527, 528, 8, 64, 5, 2, 528, 529, 8, 64, 8, 2, 529, 130, 3, 2, 2, 2, 530, 531, 7, 46, 2, 2, 531, 532, 3, 2, 2, 2, 532, 533, 8, 65, 9, 2, 533, 132, 3, 2, 2, 2, 534, 535, 7, 63, 2, 2, 535, 536, 3, 2, 2, 2, 536, 537, 8, 66, 10, 2, 537, 134, 3, 2, 2, 2, 538, 540, 5, 137, 68, 2, 539, 538, 3, 2, 2, 2, 540, 541, 3, 2, 2, 2, 541, 539, 3, 2, 2, 2, 541, 542, 3, 2, 2, 2, 542, 136, 3, 2, 2, 2, 543, 545, 10, 11, 2, 2, 544, 543, 3, 2, 2, 2, 545, 546, 3, 2, 2, 2, 546, 544, 3, 2, 2, 2, 546, 547, 3, 2, 2, 2, 547, 551, 3, 2, 2, 2, 548, 549, 7, 49, 2, 2, 549, 551, 10, 12, 2, 2, 550, 544, 3, 2, 2, 2, 550, 548, 3, 2, 2, 2, 551, 138, 3, 2, 2, 2, 552, 553, 5, 119, 59, 2, 553, 140, 3, 2, 2, 2, 554, 555, 5, 23, 11, 2, 555, 556, 3, 2, 2, 2, 556, 557, 8, 70, 4, 2, 557, 142, 3, 2, 2, 2, 558, 559, 5, 25, 12, 2, 559, 560, 3, 2, 2, 2, 560, 561, 8, 71, 4, 2, 561, 144, 3, 2, 2, 2, 562, 563, 5, 27, 13, 2, 563, 564, 3, 2, 2, 2, 564, 565, 8, 72, 4, 2, 565, 146, 3, 2, 2, 2, 566, 568, 10, 13, 2, 2, 567, 566, 3, 2, 2, 2, 568, 569, 3, 2, 2, 2, 569, 567, 3, 2, 2, 2, 569, 570, 3, 2, 2, 2, 570, 571, 3, 2, 2, 2, 571, 572, 8, 73, 2, 2, 572, 148, 3, 2, 2, 2, 37, 2, 3, 4, 226, 230, 233, 242, 244, 255, 274, 279, 284, 286, 297, 305, 308, 310, 315, 320, 326, 333, 338, 344, 347, 355, 359, 486, 491, 493, 500, 502, 541, 546, 550, 569, 11, 7, 3, 2, 7, 4, 2, 2, 3, 2, 6, 2, 2, 7, 2, 2, 9, 15, 2, 9, 31, 2, 9, 23, 2, 9, 22, 2] \ No newline at end of file diff --git a/packages/kbn-monaco/src/esql/antlr/esql_lexer.tokens b/packages/kbn-monaco/src/esql/antlr/esql_lexer.tokens new file mode 100644 index 0000000000000..b39004ce4ce32 --- /dev/null +++ b/packages/kbn-monaco/src/esql/antlr/esql_lexer.tokens @@ -0,0 +1,104 @@ +EVAL=1 +EXPLAIN=2 +FROM=3 +ROW=4 +STATS=5 +WHERE=6 +SORT=7 +LIMIT=8 +PROJECT=9 +LINE_COMMENT=10 +MULTILINE_COMMENT=11 +WS=12 +PIPE=13 +STRING=14 +INTEGER_LITERAL=15 +DECIMAL_LITERAL=16 +BY=17 +AND=18 +ASC=19 +ASSIGN=20 +COMMA=21 +DESC=22 +DOT=23 +FALSE=24 +FIRST=25 +LAST=26 +LP=27 +OPENING_BRACKET=28 +CLOSING_BRACKET=29 +NOT=30 +NULL=31 +NULLS=32 +OR=33 +RP=34 +TRUE=35 +EQ=36 +NEQ=37 +LT=38 +LTE=39 +GT=40 +GTE=41 +PLUS=42 +MINUS=43 +ASTERISK=44 +SLASH=45 +PERCENT=46 +ROUND_FUNCTION_MATH=47 +AVG_FUNCTION_MATH=48 +SUM_FUNCTION_MATH=49 +MIN_FUNCTION_MATH=50 +MAX_FUNCTION_MATH=51 +UNQUOTED_IDENTIFIER=52 +QUOTED_IDENTIFIER=53 +EXPR_LINE_COMMENT=54 +EXPR_MULTILINE_COMMENT=55 +EXPR_WS=56 +SRC_UNQUOTED_IDENTIFIER=57 +SRC_QUOTED_IDENTIFIER=58 +SRC_LINE_COMMENT=59 +SRC_MULTILINE_COMMENT=60 +SRC_WS=61 +UNKNOWN_CMD=62 +'eval'=1 +'explain'=2 +'from'=3 +'row'=4 +'stats'=5 +'where'=6 +'sort'=7 +'limit'=8 +'project'=9 +'by'=17 +'and'=18 +'asc'=19 +'desc'=22 +'.'=23 +'false'=24 +'first'=25 +'last'=26 +'('=27 +'['=28 +']'=29 +'not'=30 +'null'=31 +'nulls'=32 +'or'=33 +')'=34 +'true'=35 +'=='=36 +'!='=37 +'<'=38 +'<='=39 +'>'=40 +'>='=41 +'+'=42 +'-'=43 +'*'=44 +'/'=45 +'%'=46 +'round'=47 +'avg'=48 +'sum'=49 +'min'=50 +'max'=51 diff --git a/packages/kbn-monaco/src/esql/antlr/esql_lexer.ts b/packages/kbn-monaco/src/esql/antlr/esql_lexer.ts new file mode 100644 index 0000000000000..de10e76f9c72a --- /dev/null +++ b/packages/kbn-monaco/src/esql/antlr/esql_lexer.ts @@ -0,0 +1,457 @@ +// @ts-nocheck +// Generated from src/esql/antlr/esql_lexer.g4 by ANTLR 4.7.3-SNAPSHOT + + +import { ATN } from "antlr4ts/atn/ATN"; +import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; +import { CharStream } from "antlr4ts/CharStream"; +import { Lexer } from "antlr4ts/Lexer"; +import { LexerATNSimulator } from "antlr4ts/atn/LexerATNSimulator"; +import { NotNull } from "antlr4ts/Decorators"; +import { Override } from "antlr4ts/Decorators"; +import { RuleContext } from "antlr4ts/RuleContext"; +import { Vocabulary } from "antlr4ts/Vocabulary"; +import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; + +import * as Utils from "antlr4ts/misc/Utils"; + + +export class esql_lexer extends Lexer { + public static readonly EVAL = 1; + public static readonly EXPLAIN = 2; + public static readonly FROM = 3; + public static readonly ROW = 4; + public static readonly STATS = 5; + public static readonly WHERE = 6; + public static readonly SORT = 7; + public static readonly LIMIT = 8; + public static readonly PROJECT = 9; + public static readonly LINE_COMMENT = 10; + public static readonly MULTILINE_COMMENT = 11; + public static readonly WS = 12; + public static readonly PIPE = 13; + public static readonly STRING = 14; + public static readonly INTEGER_LITERAL = 15; + public static readonly DECIMAL_LITERAL = 16; + public static readonly BY = 17; + public static readonly AND = 18; + public static readonly ASC = 19; + public static readonly ASSIGN = 20; + public static readonly COMMA = 21; + public static readonly DESC = 22; + public static readonly DOT = 23; + public static readonly FALSE = 24; + public static readonly FIRST = 25; + public static readonly LAST = 26; + public static readonly LP = 27; + public static readonly OPENING_BRACKET = 28; + public static readonly CLOSING_BRACKET = 29; + public static readonly NOT = 30; + public static readonly NULL = 31; + public static readonly NULLS = 32; + public static readonly OR = 33; + public static readonly RP = 34; + public static readonly TRUE = 35; + public static readonly EQ = 36; + public static readonly NEQ = 37; + public static readonly LT = 38; + public static readonly LTE = 39; + public static readonly GT = 40; + public static readonly GTE = 41; + public static readonly PLUS = 42; + public static readonly MINUS = 43; + public static readonly ASTERISK = 44; + public static readonly SLASH = 45; + public static readonly PERCENT = 46; + public static readonly ROUND_FUNCTION_MATH = 47; + public static readonly AVG_FUNCTION_MATH = 48; + public static readonly SUM_FUNCTION_MATH = 49; + public static readonly MIN_FUNCTION_MATH = 50; + public static readonly MAX_FUNCTION_MATH = 51; + public static readonly UNQUOTED_IDENTIFIER = 52; + public static readonly QUOTED_IDENTIFIER = 53; + public static readonly EXPR_LINE_COMMENT = 54; + public static readonly EXPR_MULTILINE_COMMENT = 55; + public static readonly EXPR_WS = 56; + public static readonly SRC_UNQUOTED_IDENTIFIER = 57; + public static readonly SRC_QUOTED_IDENTIFIER = 58; + public static readonly SRC_LINE_COMMENT = 59; + public static readonly SRC_MULTILINE_COMMENT = 60; + public static readonly SRC_WS = 61; + public static readonly UNKNOWN_CMD = 62; + public static readonly EXPRESSION = 1; + public static readonly SOURCE_IDENTIFIERS = 2; + + // tslint:disable:no-trailing-whitespace + public static readonly channelNames: string[] = [ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + ]; + + // tslint:disable:no-trailing-whitespace + public static readonly modeNames: string[] = [ + "DEFAULT_MODE", "EXPRESSION", "SOURCE_IDENTIFIERS", + ]; + + public static readonly ruleNames: string[] = [ + "EVAL", "EXPLAIN", "FROM", "ROW", "STATS", "WHERE", "SORT", "LIMIT", "PROJECT", + "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", "DIGIT", "LETTER", + "ESCAPE_SEQUENCE", "UNESCAPED_CHARS", "EXPONENT", "STRING", "INTEGER_LITERAL", + "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", "COMMA", "DESC", "DOT", + "FALSE", "FIRST", "LAST", "LP", "OPENING_BRACKET", "CLOSING_BRACKET", + "NOT", "NULL", "NULLS", "OR", "RP", "TRUE", "EQ", "NEQ", "LT", "LTE", + "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", "ROUND_FUNCTION_MATH", + "AVG_FUNCTION_MATH", "SUM_FUNCTION_MATH", "MIN_FUNCTION_MATH", "MAX_FUNCTION_MATH", + "UNQUOTED_IDENTIFIER", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", + "EXPR_WS", "SRC_PIPE", "SRC_CLOSING_BRACKET", "SRC_COMMA", "SRC_ASSIGN", + "SRC_UNQUOTED_IDENTIFIER", "SRC_UNQUOTED_IDENTIFIER_PART", "SRC_QUOTED_IDENTIFIER", + "SRC_LINE_COMMENT", "SRC_MULTILINE_COMMENT", "SRC_WS", "UNKNOWN_CMD", + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, "'eval'", "'explain'", "'from'", "'row'", "'stats'", "'where'", + "'sort'", "'limit'", "'project'", undefined, undefined, undefined, undefined, + undefined, undefined, undefined, "'by'", "'and'", "'asc'", undefined, + undefined, "'desc'", "'.'", "'false'", "'first'", "'last'", "'('", "'['", + "']'", "'not'", "'null'", "'nulls'", "'or'", "')'", "'true'", "'=='", + "'!='", "'<'", "'<='", "'>'", "'>='", "'+'", "'-'", "'*'", "'/'", "'%'", + "'round'", "'avg'", "'sum'", "'min'", "'max'", + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, "EVAL", "EXPLAIN", "FROM", "ROW", "STATS", "WHERE", "SORT", + "LIMIT", "PROJECT", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", + "STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", + "COMMA", "DESC", "DOT", "FALSE", "FIRST", "LAST", "LP", "OPENING_BRACKET", + "CLOSING_BRACKET", "NOT", "NULL", "NULLS", "OR", "RP", "TRUE", "EQ", "NEQ", + "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", + "ROUND_FUNCTION_MATH", "AVG_FUNCTION_MATH", "SUM_FUNCTION_MATH", "MIN_FUNCTION_MATH", + "MAX_FUNCTION_MATH", "UNQUOTED_IDENTIFIER", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", + "EXPR_MULTILINE_COMMENT", "EXPR_WS", "SRC_UNQUOTED_IDENTIFIER", "SRC_QUOTED_IDENTIFIER", + "SRC_LINE_COMMENT", "SRC_MULTILINE_COMMENT", "SRC_WS", "UNKNOWN_CMD", + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(esql_lexer._LITERAL_NAMES, esql_lexer._SYMBOLIC_NAMES, []); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return esql_lexer.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + + constructor(input: CharStream) { + super(input); + this._interp = new LexerATNSimulator(esql_lexer._ATN, this); + } + + // @Override + public get grammarFileName(): string { return "esql_lexer.g4"; } + + // @Override + public get ruleNames(): string[] { return esql_lexer.ruleNames; } + + // @Override + public get serializedATN(): string { return esql_lexer._serializedATN; } + + // @Override + public get channelNames(): string[] { return esql_lexer.channelNames; } + + // @Override + public get modeNames(): string[] { return esql_lexer.modeNames; } + + private static readonly _serializedATNSegments: number = 2; + private static readonly _serializedATNSegment0: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x02@\u023D\b\x01" + + "\b\x01\b\x01\x04\x02\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04" + + "\x06\t\x06\x04\x07\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f" + + "\t\f\x04\r\t\r\x04\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11" + + "\x04\x12\t\x12\x04\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16" + + "\x04\x17\t\x17\x04\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B" + + "\x04\x1C\t\x1C\x04\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!" + + "\t!\x04\"\t\"\x04#\t#\x04$\t$\x04%\t%\x04&\t&\x04\'\t\'\x04(\t(\x04)\t" + + ")\x04*\t*\x04+\t+\x04,\t,\x04-\t-\x04.\t.\x04/\t/\x040\t0\x041\t1\x04" + + "2\t2\x043\t3\x044\t4\x045\t5\x046\t6\x047\t7\x048\t8\x049\t9\x04:\t:\x04" + + ";\t;\x04<\t<\x04=\t=\x04>\t>\x04?\t?\x04@\t@\x04A\tA\x04B\tB\x04C\tC\x04" + + "D\tD\x04E\tE\x04F\tF\x04G\tG\x04H\tH\x04I\tI\x03\x02\x03\x02\x03\x02\x03" + + "\x02\x03\x02\x03\x02\x03\x02\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03" + + "\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x04\x03\x04\x03\x04\x03\x04\x03" + + "\x04\x03\x04\x03\x04\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03" + + "\x06\x03\x06\x03\x06\x03\x06\x03\x06\x03\x06\x03\x06\x03\x06\x03\x07\x03" + + "\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\x07\x03\b\x03\b\x03\b" + + "\x03\b\x03\b\x03\b\x03\b\x03\t\x03\t\x03\t\x03\t\x03\t\x03\t\x03\t\x03" + + "\t\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\v\x03" + + "\v\x03\v\x03\v\x07\v\xE1\n\v\f\v\x0E\v\xE4\v\v\x03\v\x05\v\xE7\n\v\x03" + + "\v\x05\v\xEA\n\v\x03\v\x03\v\x03\f\x03\f\x03\f\x03\f\x03\f\x07\f\xF3\n" + + "\f\f\f\x0E\f\xF6\v\f\x03\f\x03\f\x03\f\x03\f\x03\f\x03\r\x06\r\xFE\n\r" + + "\r\r\x0E\r\xFF\x03\r\x03\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x03\x0F\x03" + + "\x0F\x03\x10\x03\x10\x03\x11\x03\x11\x03\x11\x03\x12\x03\x12\x03\x13\x03" + + "\x13\x05\x13\u0113\n\x13\x03\x13\x06\x13\u0116\n\x13\r\x13\x0E\x13\u0117" + + "\x03\x14\x03\x14\x03\x14\x07\x14\u011D\n\x14\f\x14\x0E\x14\u0120\v\x14" + + "\x03\x14\x03\x14\x03\x14\x03\x14\x03\x14\x03\x14\x07\x14\u0128\n\x14\f" + + "\x14\x0E\x14\u012B\v\x14\x03\x14\x03\x14\x03\x14\x03\x14\x03\x14\x05\x14" + + "\u0132\n\x14\x03\x14\x05\x14\u0135\n\x14\x05\x14\u0137\n\x14\x03\x15\x06" + + "\x15\u013A\n\x15\r\x15\x0E\x15\u013B\x03\x16\x06\x16\u013F\n\x16\r\x16" + + "\x0E\x16\u0140\x03\x16\x03\x16\x07\x16\u0145\n\x16\f\x16\x0E\x16\u0148" + + "\v\x16\x03\x16\x03\x16\x06\x16\u014C\n\x16\r\x16\x0E\x16\u014D\x03\x16" + + "\x06\x16\u0151\n\x16\r\x16\x0E\x16\u0152\x03\x16\x03\x16\x07\x16\u0157" + + "\n\x16\f\x16\x0E\x16\u015A\v\x16\x05\x16\u015C\n\x16\x03\x16\x03\x16\x03" + + "\x16\x03\x16\x06\x16\u0162\n\x16\r\x16\x0E\x16\u0163\x03\x16\x03\x16\x05" + + "\x16\u0168\n\x16\x03\x17\x03\x17\x03\x17\x03\x18\x03\x18\x03\x18\x03\x18" + + "\x03\x19\x03\x19\x03\x19\x03\x19\x03\x1A\x03\x1A\x03\x1B\x03\x1B\x03\x1C" + + "\x03\x1C\x03\x1C\x03\x1C\x03\x1C\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x03\x1E" + + "\x03\x1E\x03\x1E\x03\x1E\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F\x03\x1F" + + "\x03 \x03 \x03 \x03 \x03 \x03!\x03!\x03\"\x03\"\x03\"\x03\"\x03#\x03#" + + "\x03#\x03#\x03#\x03$\x03$\x03$\x03$\x03%\x03%\x03%\x03%\x03%\x03&\x03" + + "&\x03&\x03&\x03&\x03&\x03\'\x03\'\x03\'\x03(\x03(\x03)\x03)\x03)\x03)" + + "\x03)\x03*\x03*\x03*\x03+\x03+\x03+\x03,\x03,\x03-\x03-\x03-\x03.\x03" + + ".\x03/\x03/\x03/\x030\x030\x031\x031\x032\x032\x033\x033\x034\x034\x03" + + "5\x035\x035\x035\x035\x035\x036\x036\x036\x036\x037\x037\x037\x037\x03" + + "8\x038\x038\x038\x039\x039\x039\x039\x03:\x03:\x05:\u01E7\n:\x03:\x03" + + ":\x03:\x07:\u01EC\n:\f:\x0E:\u01EF\v:\x03;\x03;\x03;\x03;\x07;\u01F5\n" + + ";\f;\x0E;\u01F8\v;\x03;\x03;\x03<\x03<\x03<\x03<\x03=\x03=\x03=\x03=\x03" + + ">\x03>\x03>\x03>\x03?\x03?\x03?\x03?\x03?\x03@\x03@\x03@\x03@\x03@\x03" + + "@\x03A\x03A\x03A\x03A\x03B\x03B\x03B\x03B\x03C\x06C\u021C\nC\rC\x0EC\u021D" + + "\x03D\x06D\u0221\nD\rD\x0ED\u0222\x03D\x03D\x05D\u0227\nD\x03E\x03E\x03" + + "F\x03F\x03F\x03F\x03G\x03G\x03G\x03G\x03H\x03H\x03H\x03H\x03I\x06I\u0238" + + "\nI\rI\x0EI\u0239\x03I\x03I\x04\xF4\u0129\x02\x02J\x05\x02\x03\x07\x02" + + "\x04\t\x02\x05\v\x02\x06\r\x02\x07\x0F\x02\b\x11\x02\t\x13\x02\n\x15\x02" + + "\v\x17\x02\f\x19\x02\r\x1B\x02\x0E\x1D\x02\x0F\x1F\x02\x02!\x02\x02#\x02" + + "\x02%\x02\x02\'\x02\x02)\x02\x10+\x02\x11-\x02\x12/\x02\x131\x02\x143" + + "\x02\x155\x02\x167\x02\x179\x02\x18;\x02\x19=\x02\x1A?\x02\x1BA\x02\x1C" + + "C\x02\x1DE\x02\x1EG\x02\x1FI\x02 K\x02!M\x02\"O\x02#Q\x02$S\x02%U\x02" + + "&W\x02\'Y\x02([\x02)]\x02*_\x02+a\x02,c\x02-e\x02.g\x02/i\x020k\x021m" + + "\x022o\x023q\x024s\x025u\x026w\x027y\x028{\x029}\x02:\x7F\x02\x02\x81" + + "\x02\x02\x83\x02\x02\x85\x02\x02\x87\x02;\x89\x02\x02\x8B\x02<\x8D\x02" + + "=\x8F\x02>\x91\x02?\x93\x02@\x05\x02\x03\x04\x0E\x04\x02\f\f\x0F\x0F\x05" + + "\x02\v\f\x0F\x0F\"\"\x03\x022;\x04\x02C\\c|\x07\x02$$^^ppttvv\x06\x02" + + "\f\f\x0F\x0F$$^^\x04\x02GGgg\x04\x02--//\x03\x02bb\f\x02\v\f\x0F\x0F\"" + + "\"..11??]]__bb~~\x04\x02,,11\b\x02\v\f\x0F\x0F\"\"11]]__\x02\u0257\x02" + + "\x05\x03\x02\x02\x02\x02\x07\x03\x02\x02\x02\x02\t\x03\x02\x02\x02\x02" + + "\v\x03\x02\x02\x02\x02\r\x03\x02\x02\x02\x02\x0F\x03\x02\x02\x02\x02\x11" + + "\x03\x02\x02\x02\x02\x13\x03\x02\x02\x02\x02\x15\x03\x02\x02\x02\x02\x17" + + "\x03\x02\x02\x02\x02\x19\x03\x02\x02\x02\x02\x1B\x03\x02\x02\x02\x03\x1D" + + "\x03\x02\x02\x02\x03)\x03\x02\x02\x02\x03+\x03\x02\x02\x02\x03-\x03\x02" + + "\x02\x02\x03/\x03\x02\x02\x02\x031\x03\x02\x02\x02\x033\x03\x02\x02\x02" + + "\x035\x03\x02\x02\x02\x037\x03\x02\x02\x02\x039\x03\x02\x02\x02\x03;\x03" + + "\x02\x02\x02\x03=\x03\x02\x02\x02\x03?\x03\x02\x02\x02\x03A\x03\x02\x02" + + "\x02\x03C\x03\x02\x02\x02\x03E\x03\x02\x02\x02\x03G\x03\x02\x02\x02\x03" + + "I\x03\x02\x02\x02\x03K\x03\x02\x02\x02\x03M\x03\x02\x02\x02\x03O\x03\x02" + + "\x02\x02\x03Q\x03\x02\x02\x02\x03S\x03\x02\x02\x02\x03U\x03\x02\x02\x02" + + "\x03W\x03\x02\x02\x02\x03Y\x03\x02\x02\x02\x03[\x03\x02\x02\x02\x03]\x03" + + "\x02\x02\x02\x03_\x03\x02\x02\x02\x03a\x03\x02\x02\x02\x03c\x03\x02\x02" + + "\x02\x03e\x03\x02\x02\x02\x03g\x03\x02\x02\x02\x03i\x03\x02\x02\x02\x03" + + "k\x03\x02\x02\x02\x03m\x03\x02\x02\x02\x03o\x03\x02\x02\x02\x03q\x03\x02" + + "\x02\x02\x03s\x03\x02\x02\x02\x03u\x03\x02\x02\x02\x03w\x03\x02\x02\x02" + + "\x03y\x03\x02\x02\x02\x03{\x03\x02\x02\x02\x03}\x03\x02\x02\x02\x04\x7F" + + "\x03\x02\x02\x02\x04\x81\x03\x02\x02\x02\x04\x83\x03\x02\x02\x02\x04\x85" + + "\x03\x02\x02\x02\x04\x87\x03\x02\x02\x02\x04\x8B\x03\x02\x02\x02\x04\x8D" + + "\x03\x02\x02\x02\x04\x8F\x03\x02\x02\x02\x04\x91\x03\x02\x02\x02\x04\x93" + + "\x03\x02\x02\x02\x05\x95\x03\x02\x02\x02\x07\x9C\x03\x02\x02\x02\t\xA6" + + "\x03\x02\x02\x02\v\xAD\x03\x02\x02\x02\r\xB3\x03\x02\x02\x02\x0F\xBB\x03" + + "\x02\x02\x02\x11\xC3\x03\x02\x02\x02\x13\xCA\x03\x02\x02\x02\x15\xD2\x03" + + "\x02\x02\x02\x17\xDC\x03\x02\x02\x02\x19\xED\x03\x02\x02\x02\x1B\xFD\x03" + + "\x02\x02\x02\x1D\u0103\x03\x02\x02\x02\x1F\u0107\x03\x02\x02\x02!\u0109" + + "\x03\x02\x02\x02#\u010B\x03\x02\x02\x02%\u010E\x03\x02\x02\x02\'\u0110" + + "\x03\x02\x02\x02)\u0136\x03\x02\x02\x02+\u0139\x03\x02\x02\x02-\u0167" + + "\x03\x02\x02\x02/\u0169\x03\x02\x02\x021\u016C\x03\x02\x02\x023\u0170" + + "\x03\x02\x02\x025\u0174\x03\x02\x02\x027\u0176\x03\x02\x02\x029\u0178" + + "\x03\x02\x02\x02;\u017D\x03\x02\x02\x02=\u017F\x03\x02\x02\x02?\u0185" + + "\x03\x02\x02\x02A\u018B\x03\x02\x02\x02C\u0190\x03\x02\x02\x02E\u0192" + + "\x03\x02\x02\x02G\u0196\x03\x02\x02\x02I\u019B\x03\x02\x02\x02K\u019F" + + "\x03\x02\x02\x02M\u01A4\x03\x02\x02\x02O\u01AA\x03\x02\x02\x02Q\u01AD" + + "\x03\x02\x02\x02S\u01AF\x03\x02\x02\x02U\u01B4\x03\x02\x02\x02W\u01B7" + + "\x03\x02\x02\x02Y\u01BA\x03\x02\x02\x02[\u01BC\x03\x02\x02\x02]\u01BF" + + "\x03\x02\x02\x02_\u01C1\x03\x02\x02\x02a\u01C4\x03\x02\x02\x02c\u01C6" + + "\x03\x02\x02\x02e\u01C8\x03\x02\x02\x02g\u01CA\x03\x02\x02\x02i\u01CC" + + "\x03\x02\x02\x02k\u01CE\x03\x02\x02\x02m\u01D4\x03\x02\x02\x02o\u01D8" + + "\x03\x02\x02\x02q\u01DC\x03\x02\x02\x02s\u01E0\x03\x02\x02\x02u\u01E6" + + "\x03\x02\x02\x02w\u01F0\x03\x02\x02\x02y\u01FB\x03\x02\x02\x02{\u01FF" + + "\x03\x02\x02\x02}\u0203\x03\x02\x02\x02\x7F\u0207\x03\x02\x02\x02\x81" + + "\u020C\x03\x02\x02\x02\x83\u0212\x03\x02\x02\x02\x85\u0216\x03\x02\x02" + + "\x02\x87\u021B\x03\x02\x02\x02\x89\u0226\x03\x02\x02\x02\x8B\u0228\x03" + + "\x02\x02\x02\x8D\u022A\x03\x02\x02\x02\x8F\u022E\x03\x02\x02\x02\x91\u0232" + + "\x03\x02\x02\x02\x93\u0237\x03\x02\x02\x02\x95\x96\x07g\x02\x02\x96\x97" + + "\x07x\x02\x02\x97\x98\x07c\x02\x02\x98\x99\x07n\x02\x02\x99\x9A\x03\x02" + + "\x02\x02\x9A\x9B\b\x02\x02\x02\x9B\x06\x03\x02\x02\x02\x9C\x9D\x07g\x02" + + "\x02\x9D\x9E\x07z\x02\x02\x9E\x9F\x07r\x02\x02\x9F\xA0\x07n\x02\x02\xA0" + + "\xA1\x07c\x02\x02\xA1\xA2\x07k\x02\x02\xA2\xA3\x07p\x02\x02\xA3\xA4\x03" + + "\x02\x02\x02\xA4\xA5\b\x03\x02\x02\xA5\b\x03\x02\x02\x02\xA6\xA7\x07h" + + "\x02\x02\xA7\xA8\x07t\x02\x02\xA8\xA9\x07q\x02\x02\xA9\xAA\x07o\x02\x02" + + "\xAA\xAB\x03\x02\x02\x02\xAB\xAC\b\x04\x03\x02\xAC\n\x03\x02\x02\x02\xAD" + + "\xAE\x07t\x02\x02\xAE\xAF\x07q\x02\x02\xAF\xB0\x07y\x02\x02\xB0\xB1\x03" + + "\x02\x02\x02\xB1\xB2\b\x05\x02\x02\xB2\f\x03\x02\x02\x02\xB3\xB4\x07u" + + "\x02\x02\xB4\xB5\x07v\x02\x02\xB5\xB6\x07c\x02\x02\xB6\xB7\x07v\x02\x02" + + "\xB7\xB8\x07u\x02\x02\xB8\xB9\x03\x02\x02\x02\xB9\xBA\b\x06\x02\x02\xBA" + + "\x0E\x03\x02\x02\x02\xBB\xBC\x07y\x02\x02\xBC\xBD\x07j\x02\x02\xBD\xBE" + + "\x07g\x02\x02\xBE\xBF\x07t\x02\x02\xBF\xC0\x07g\x02\x02\xC0\xC1\x03\x02" + + "\x02\x02\xC1\xC2\b\x07\x02\x02\xC2\x10\x03\x02\x02\x02\xC3\xC4\x07u\x02" + + "\x02\xC4\xC5\x07q\x02\x02\xC5\xC6\x07t\x02\x02\xC6\xC7\x07v\x02\x02\xC7" + + "\xC8\x03\x02\x02\x02\xC8\xC9\b\b\x02\x02\xC9\x12\x03\x02\x02\x02\xCA\xCB" + + "\x07n\x02\x02\xCB\xCC\x07k\x02\x02\xCC\xCD\x07o\x02\x02\xCD\xCE\x07k\x02" + + "\x02\xCE\xCF\x07v\x02\x02\xCF\xD0\x03\x02\x02\x02\xD0\xD1\b\t\x02\x02" + + "\xD1\x14\x03\x02\x02\x02\xD2\xD3\x07r\x02\x02\xD3\xD4\x07t\x02\x02\xD4" + + "\xD5\x07q\x02\x02\xD5\xD6\x07l\x02\x02\xD6\xD7\x07g\x02\x02\xD7\xD8\x07" + + "e\x02\x02\xD8\xD9\x07v\x02\x02\xD9\xDA\x03\x02\x02\x02\xDA\xDB\b\n\x03" + + "\x02\xDB\x16\x03\x02\x02\x02\xDC\xDD\x071\x02\x02\xDD\xDE\x071\x02\x02" + + "\xDE\xE2\x03\x02\x02\x02\xDF\xE1\n\x02\x02\x02\xE0\xDF\x03\x02\x02\x02" + + "\xE1\xE4\x03\x02\x02\x02\xE2\xE0\x03\x02\x02\x02\xE2\xE3\x03\x02\x02\x02" + + "\xE3\xE6\x03\x02\x02\x02\xE4\xE2\x03\x02\x02\x02\xE5\xE7\x07\x0F\x02\x02" + + "\xE6\xE5\x03\x02\x02\x02\xE6\xE7\x03\x02\x02\x02\xE7\xE9\x03\x02\x02\x02" + + "\xE8\xEA\x07\f\x02\x02\xE9\xE8\x03\x02\x02\x02\xE9\xEA\x03\x02\x02\x02" + + "\xEA\xEB\x03\x02\x02\x02\xEB\xEC\b\v\x04\x02\xEC\x18\x03\x02\x02\x02\xED" + + "\xEE\x071\x02\x02\xEE\xEF\x07,\x02\x02\xEF\xF4\x03\x02\x02\x02\xF0\xF3" + + "\x05\x19\f\x02\xF1\xF3\v\x02\x02\x02\xF2\xF0\x03\x02\x02\x02\xF2\xF1\x03" + + "\x02\x02\x02\xF3\xF6\x03\x02\x02\x02\xF4\xF5\x03\x02\x02\x02\xF4\xF2\x03" + + "\x02\x02\x02\xF5\xF7\x03\x02\x02\x02\xF6\xF4\x03\x02\x02\x02\xF7\xF8\x07" + + ",\x02\x02\xF8\xF9\x071\x02\x02\xF9\xFA\x03\x02\x02\x02\xFA\xFB\b\f\x04" + + "\x02\xFB\x1A\x03\x02\x02\x02\xFC\xFE\t\x03\x02\x02\xFD\xFC\x03\x02\x02" + + "\x02\xFE\xFF\x03\x02\x02\x02\xFF\xFD\x03\x02\x02\x02\xFF\u0100\x03\x02" + + "\x02\x02\u0100\u0101\x03\x02\x02\x02\u0101\u0102\b\r\x04\x02\u0102\x1C" + + "\x03\x02\x02\x02\u0103\u0104\x07~\x02\x02\u0104\u0105\x03\x02\x02\x02" + + "\u0105\u0106\b\x0E\x05\x02\u0106\x1E\x03\x02\x02\x02\u0107\u0108\t\x04" + + "\x02\x02\u0108 \x03\x02\x02\x02\u0109\u010A\t\x05\x02\x02\u010A\"\x03" + + "\x02\x02\x02\u010B\u010C\x07^\x02\x02\u010C\u010D\t\x06\x02\x02\u010D" + + "$\x03\x02\x02\x02\u010E\u010F\n\x07\x02\x02\u010F&\x03\x02\x02\x02\u0110" + + "\u0112\t\b\x02\x02\u0111\u0113\t\t\x02\x02\u0112\u0111\x03\x02\x02\x02" + + "\u0112\u0113\x03\x02\x02\x02\u0113\u0115\x03\x02\x02\x02\u0114\u0116\x05" + + "\x1F\x0F\x02\u0115\u0114\x03\x02\x02\x02\u0116\u0117\x03\x02\x02\x02\u0117" + + "\u0115\x03\x02\x02\x02\u0117\u0118\x03\x02\x02\x02\u0118(\x03\x02\x02" + + "\x02\u0119\u011E\x07$\x02\x02\u011A\u011D\x05#\x11\x02\u011B\u011D\x05" + + "%\x12\x02\u011C\u011A\x03\x02\x02\x02\u011C\u011B\x03\x02\x02\x02\u011D" + + "\u0120\x03\x02\x02\x02\u011E\u011C\x03\x02\x02\x02\u011E\u011F\x03\x02" + + "\x02\x02\u011F\u0121\x03\x02\x02\x02\u0120\u011E\x03\x02\x02\x02\u0121" + + "\u0137\x07$\x02\x02\u0122\u0123\x07$\x02\x02\u0123\u0124\x07$\x02\x02" + + "\u0124\u0125\x07$\x02\x02\u0125\u0129\x03\x02\x02\x02\u0126\u0128\n\x02" + + "\x02\x02\u0127\u0126\x03\x02\x02\x02\u0128\u012B\x03\x02\x02\x02\u0129" + + "\u012A\x03\x02\x02\x02\u0129\u0127\x03\x02\x02\x02\u012A\u012C\x03\x02" + + "\x02\x02\u012B\u0129\x03\x02\x02\x02\u012C\u012D\x07$\x02\x02\u012D\u012E" + + "\x07$\x02\x02\u012E\u012F\x07$\x02\x02\u012F\u0131\x03\x02\x02\x02\u0130" + + "\u0132\x07$\x02\x02\u0131\u0130\x03\x02\x02\x02\u0131\u0132\x03\x02\x02" + + "\x02\u0132\u0134\x03\x02\x02\x02\u0133\u0135\x07$\x02\x02\u0134\u0133" + + "\x03\x02\x02\x02\u0134\u0135\x03\x02\x02\x02\u0135\u0137\x03\x02\x02\x02" + + "\u0136\u0119\x03\x02\x02\x02\u0136\u0122\x03\x02\x02\x02\u0137*\x03\x02" + + "\x02\x02\u0138\u013A\x05\x1F\x0F\x02\u0139\u0138\x03\x02\x02\x02\u013A" + + "\u013B\x03\x02\x02\x02\u013B\u0139\x03\x02\x02\x02\u013B\u013C\x03\x02" + + "\x02\x02\u013C,\x03\x02\x02\x02\u013D\u013F\x05\x1F\x0F\x02\u013E\u013D" + + "\x03\x02\x02\x02\u013F\u0140\x03\x02\x02\x02\u0140\u013E\x03\x02\x02\x02" + + "\u0140\u0141\x03\x02\x02\x02\u0141\u0142\x03\x02\x02\x02\u0142\u0146\x05" + + ";\x1D\x02\u0143\u0145\x05\x1F\x0F\x02\u0144\u0143\x03\x02\x02\x02\u0145" + + "\u0148\x03\x02\x02\x02\u0146\u0144\x03\x02\x02\x02\u0146\u0147\x03\x02" + + "\x02\x02\u0147\u0168\x03\x02\x02\x02\u0148\u0146\x03\x02\x02\x02\u0149" + + "\u014B\x05;\x1D\x02\u014A\u014C\x05\x1F\x0F\x02\u014B\u014A\x03\x02\x02" + + "\x02\u014C\u014D\x03\x02\x02\x02\u014D\u014B\x03\x02\x02\x02\u014D\u014E" + + "\x03\x02\x02\x02\u014E\u0168\x03\x02\x02\x02\u014F\u0151\x05\x1F\x0F\x02" + + "\u0150\u014F\x03\x02\x02\x02\u0151\u0152\x03\x02\x02\x02\u0152\u0150\x03" + + "\x02\x02\x02\u0152\u0153\x03\x02\x02\x02\u0153\u015B\x03\x02\x02\x02\u0154" + + "\u0158\x05;\x1D\x02\u0155\u0157\x05\x1F\x0F\x02\u0156\u0155\x03\x02\x02" + + "\x02\u0157\u015A\x03\x02\x02\x02\u0158\u0156\x03\x02\x02\x02\u0158\u0159" + + "\x03\x02\x02\x02\u0159\u015C\x03\x02\x02\x02\u015A\u0158\x03\x02\x02\x02" + + "\u015B\u0154\x03\x02\x02\x02\u015B\u015C\x03\x02\x02\x02\u015C\u015D\x03" + + "\x02\x02\x02\u015D\u015E\x05\'\x13\x02\u015E\u0168\x03\x02\x02\x02\u015F" + + "\u0161\x05;\x1D\x02\u0160\u0162\x05\x1F\x0F\x02\u0161\u0160\x03\x02\x02" + + "\x02\u0162\u0163\x03\x02\x02\x02\u0163\u0161\x03\x02\x02\x02\u0163\u0164" + + "\x03\x02\x02\x02\u0164\u0165\x03\x02\x02\x02\u0165\u0166\x05\'\x13\x02" + + "\u0166\u0168\x03\x02\x02\x02\u0167\u013E\x03\x02\x02\x02\u0167\u0149\x03" + + "\x02\x02\x02\u0167\u0150\x03\x02\x02\x02\u0167\u015F\x03\x02\x02\x02\u0168" + + ".\x03\x02\x02\x02\u0169\u016A\x07d\x02\x02\u016A\u016B\x07{\x02\x02\u016B" + + "0\x03\x02\x02\x02\u016C\u016D\x07c\x02\x02\u016D\u016E\x07p\x02\x02\u016E" + + "\u016F\x07f\x02\x02\u016F2\x03\x02\x02\x02\u0170\u0171\x07c\x02\x02\u0171" + + "\u0172\x07u\x02\x02\u0172\u0173\x07e\x02\x02\u01734\x03\x02\x02\x02\u0174" + + "\u0175\x07?\x02\x02\u01756\x03\x02\x02\x02\u0176\u0177\x07.\x02\x02\u0177" + + "8\x03\x02\x02\x02\u0178\u0179\x07f\x02\x02\u0179\u017A\x07g\x02\x02\u017A" + + "\u017B\x07u\x02\x02\u017B\u017C\x07e\x02\x02\u017C:\x03\x02\x02\x02\u017D" + + "\u017E\x070\x02\x02\u017E<\x03\x02\x02\x02\u017F\u0180\x07h\x02\x02\u0180" + + "\u0181\x07c\x02\x02\u0181\u0182\x07n\x02\x02\u0182\u0183\x07u\x02\x02" + + "\u0183\u0184\x07g\x02\x02\u0184>\x03\x02\x02\x02\u0185\u0186\x07h\x02" + + "\x02\u0186\u0187\x07k\x02\x02\u0187\u0188\x07t\x02\x02\u0188\u0189\x07" + + "u\x02\x02\u0189\u018A\x07v\x02\x02\u018A@\x03\x02\x02\x02\u018B\u018C" + + "\x07n\x02\x02\u018C\u018D\x07c\x02\x02\u018D\u018E\x07u\x02\x02\u018E" + + "\u018F\x07v\x02\x02\u018FB\x03\x02\x02\x02\u0190\u0191\x07*\x02\x02\u0191" + + "D\x03\x02\x02\x02\u0192\u0193\x07]\x02\x02\u0193\u0194\x03\x02\x02\x02" + + "\u0194\u0195\b\"\x06\x02\u0195F\x03\x02\x02\x02\u0196\u0197\x07_\x02\x02" + + "\u0197\u0198\x03\x02\x02\x02\u0198\u0199\b#\x05\x02\u0199\u019A\b#\x05" + + "\x02\u019AH\x03\x02\x02\x02\u019B\u019C\x07p\x02\x02\u019C\u019D\x07q" + + "\x02\x02\u019D\u019E\x07v\x02\x02\u019EJ\x03\x02\x02\x02\u019F\u01A0\x07" + + "p\x02\x02\u01A0\u01A1\x07w\x02\x02\u01A1\u01A2\x07n\x02\x02\u01A2\u01A3" + + "\x07n\x02\x02\u01A3L\x03\x02\x02\x02\u01A4\u01A5\x07p\x02\x02\u01A5\u01A6" + + "\x07w\x02\x02\u01A6\u01A7\x07n\x02\x02\u01A7\u01A8\x07n\x02\x02\u01A8" + + "\u01A9\x07u\x02\x02\u01A9N\x03\x02\x02\x02\u01AA\u01AB\x07q\x02\x02\u01AB" + + "\u01AC\x07t\x02\x02\u01ACP\x03\x02\x02\x02\u01AD\u01AE\x07+\x02\x02\u01AE" + + "R\x03\x02\x02\x02\u01AF\u01B0\x07v\x02\x02\u01B0\u01B1\x07t\x02\x02\u01B1" + + "\u01B2\x07w\x02\x02\u01B2\u01B3\x07g\x02\x02\u01B3T\x03\x02\x02\x02\u01B4" + + "\u01B5\x07?\x02\x02\u01B5\u01B6\x07?\x02\x02\u01B6V\x03\x02\x02\x02\u01B7" + + "\u01B8\x07#\x02\x02\u01B8\u01B9\x07?\x02\x02\u01B9X\x03\x02\x02\x02\u01BA" + + "\u01BB\x07>\x02\x02\u01BBZ\x03\x02\x02\x02\u01BC\u01BD\x07>\x02\x02\u01BD" + + "\u01BE\x07?\x02\x02\u01BE\\\x03\x02\x02\x02\u01BF\u01C0\x07@\x02\x02\u01C0" + + "^\x03\x02\x02\x02\u01C1\u01C2\x07@\x02\x02\u01C2\u01C3\x07?\x02\x02\u01C3" + + "`\x03\x02\x02\x02\u01C4\u01C5\x07-\x02\x02\u01C5b\x03\x02\x02\x02\u01C6" + + "\u01C7\x07/\x02\x02\u01C7d\x03\x02\x02\x02\u01C8\u01C9\x07,\x02\x02\u01C9" + + "f\x03\x02\x02\x02\u01CA\u01CB\x071\x02\x02\u01CBh\x03\x02\x02\x02\u01CC" + + "\u01CD\x07\'\x02\x02\u01CDj\x03\x02\x02\x02\u01CE\u01CF\x07t\x02\x02\u01CF" + + "\u01D0\x07q\x02\x02\u01D0\u01D1\x07w\x02\x02\u01D1\u01D2\x07p\x02\x02" + + "\u01D2\u01D3\x07f\x02\x02\u01D3l\x03\x02\x02\x02\u01D4\u01D5\x07c\x02" + + "\x02\u01D5\u01D6\x07x\x02\x02\u01D6\u01D7\x07i\x02\x02\u01D7n\x03\x02" + + "\x02\x02\u01D8\u01D9\x07u\x02\x02\u01D9\u01DA\x07w\x02\x02\u01DA\u01DB" + + "\x07o\x02\x02\u01DBp\x03\x02\x02\x02\u01DC\u01DD\x07o\x02\x02\u01DD\u01DE" + + "\x07k\x02\x02\u01DE\u01DF\x07p\x02\x02\u01DFr\x03\x02\x02\x02\u01E0\u01E1" + + "\x07o\x02\x02\u01E1\u01E2\x07c\x02\x02\u01E2\u01E3\x07z\x02\x02\u01E3" + + "t\x03\x02\x02\x02\u01E4\u01E7\x05!\x10\x02\u01E5\u01E7\x07a\x02\x02\u01E6" + + "\u01E4\x03\x02\x02\x02\u01E6\u01E5\x03\x02\x02\x02\u01E7\u01ED\x03\x02" + + "\x02\x02\u01E8\u01EC\x05!\x10\x02\u01E9\u01EC\x05\x1F\x0F\x02\u01EA\u01EC" + + "\x07a\x02\x02\u01EB\u01E8\x03\x02\x02\x02\u01EB\u01E9\x03\x02\x02\x02" + + "\u01EB\u01EA\x03\x02\x02\x02\u01EC\u01EF\x03\x02\x02\x02\u01ED\u01EB\x03" + + "\x02\x02\x02\u01ED\u01EE\x03\x02\x02\x02\u01EEv\x03\x02\x02\x02\u01EF" + + "\u01ED\x03\x02\x02\x02\u01F0\u01F6\x07b\x02\x02\u01F1\u01F5\n\n\x02\x02" + + "\u01F2\u01F3\x07b\x02\x02\u01F3\u01F5\x07b\x02\x02\u01F4\u01F1\x03\x02" + + "\x02\x02\u01F4\u01F2\x03\x02\x02\x02\u01F5\u01F8\x03\x02\x02\x02\u01F6" + + "\u01F4\x03\x02\x02\x02\u01F6\u01F7\x03\x02\x02\x02\u01F7\u01F9\x03\x02" + + "\x02\x02\u01F8\u01F6\x03\x02\x02\x02\u01F9\u01FA\x07b\x02\x02\u01FAx\x03" + + "\x02\x02\x02\u01FB\u01FC\x05\x17\v\x02\u01FC\u01FD\x03\x02\x02\x02\u01FD" + + "\u01FE\b<\x04\x02\u01FEz\x03\x02\x02\x02\u01FF\u0200\x05\x19\f\x02\u0200" + + "\u0201\x03\x02\x02\x02\u0201\u0202\b=\x04\x02\u0202|\x03\x02\x02\x02\u0203" + + "\u0204\x05\x1B\r\x02\u0204\u0205\x03\x02\x02\x02\u0205\u0206\b>\x04\x02" + + "\u0206~\x03\x02\x02\x02\u0207\u0208\x07~\x02\x02\u0208\u0209\x03\x02\x02" + + "\x02\u0209\u020A\b?\x07\x02\u020A\u020B\b?\x05\x02\u020B\x80\x03\x02\x02" + + "\x02\u020C\u020D\x07_\x02\x02\u020D\u020E\x03\x02\x02\x02\u020E\u020F" + + "\b@\x05\x02\u020F\u0210\b@\x05\x02\u0210\u0211\b@\b\x02\u0211\x82\x03" + + "\x02\x02\x02\u0212\u0213\x07.\x02\x02\u0213\u0214\x03\x02\x02\x02\u0214" + + "\u0215\bA\t\x02\u0215\x84\x03\x02\x02\x02\u0216\u0217\x07?\x02\x02\u0217" + + "\u0218\x03\x02\x02\x02\u0218\u0219\bB\n\x02\u0219\x86\x03\x02\x02\x02" + + "\u021A\u021C\x05\x89D\x02\u021B\u021A\x03\x02\x02\x02\u021C\u021D\x03" + + "\x02\x02\x02\u021D\u021B\x03\x02\x02\x02\u021D\u021E\x03\x02\x02\x02\u021E" + + "\x88\x03\x02\x02\x02\u021F\u0221\n\v\x02\x02\u0220\u021F\x03\x02\x02\x02" + + "\u0221\u0222\x03\x02\x02\x02\u0222\u0220\x03\x02\x02\x02\u0222\u0223\x03" + + "\x02\x02\x02\u0223\u0227\x03\x02\x02\x02\u0224\u0225\x071\x02\x02\u0225" + + "\u0227\n\f\x02\x02\u0226\u0220\x03\x02\x02\x02\u0226\u0224\x03\x02\x02" + + "\x02\u0227\x8A\x03\x02\x02\x02\u0228\u0229\x05w;\x02\u0229\x8C\x03\x02" + + "\x02\x02\u022A\u022B\x05\x17\v"; + private static readonly _serializedATNSegment1: string = + "\x02\u022B\u022C\x03\x02\x02\x02\u022C\u022D\bF\x04\x02\u022D\x8E\x03" + + "\x02\x02\x02\u022E\u022F\x05\x19\f\x02\u022F\u0230\x03\x02\x02\x02\u0230" + + "\u0231\bG\x04\x02\u0231\x90\x03\x02\x02\x02\u0232\u0233\x05\x1B\r\x02" + + "\u0233\u0234\x03\x02\x02\x02\u0234\u0235\bH\x04\x02\u0235\x92\x03\x02" + + "\x02\x02\u0236\u0238\n\r\x02\x02\u0237\u0236\x03\x02\x02\x02\u0238\u0239" + + "\x03\x02\x02\x02\u0239\u0237\x03\x02\x02\x02\u0239\u023A\x03\x02\x02\x02" + + "\u023A\u023B\x03\x02\x02\x02\u023B\u023C\bI\x02\x02\u023C\x94\x03\x02" + + "\x02\x02%\x02\x03\x04\xE2\xE6\xE9\xF2\xF4\xFF\u0112\u0117\u011C\u011E" + + "\u0129\u0131\u0134\u0136\u013B\u0140\u0146\u014D\u0152\u0158\u015B\u0163" + + "\u0167\u01E6\u01EB\u01ED\u01F4\u01F6\u021D\u0222\u0226\u0239\v\x07\x03" + + "\x02\x07\x04\x02\x02\x03\x02\x06\x02\x02\x07\x02\x02\t\x0F\x02\t\x1F\x02" + + "\t\x17\x02\t\x16\x02"; + public static readonly _serializedATN: string = Utils.join( + [ + esql_lexer._serializedATNSegment0, + esql_lexer._serializedATNSegment1, + ], + "", + ); + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!esql_lexer.__ATN) { + esql_lexer.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(esql_lexer._serializedATN)); + } + + return esql_lexer.__ATN; + } + +} + diff --git a/packages/kbn-monaco/src/esql/antlr/esql_parser.g4 b/packages/kbn-monaco/src/esql/antlr/esql_parser.g4 new file mode 100644 index 0000000000000..f3141d22c5c8b --- /dev/null +++ b/packages/kbn-monaco/src/esql/antlr/esql_parser.g4 @@ -0,0 +1,177 @@ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +parser grammar esql_parser; + +options {tokenVocab=esql_lexer;} + +singleStatement + : query EOF + ; + +query + : sourceCommand #singleCommandQuery + | query PIPE processingCommand #compositeQuery + ; + +sourceCommand + : explainCommand + | fromCommand + | rowCommand + ; + +processingCommand + : evalCommand + | limitCommand + | projectCommand + | sortCommand + | statsCommand + | whereCommand + ; + +whereCommand + : WHERE booleanExpression + ; + +booleanExpression + : NOT booleanExpression #logicalNot + | valueExpression #booleanDefault + | left=booleanExpression operator=AND right=booleanExpression #logicalBinary + | left=booleanExpression operator=OR right=booleanExpression #logicalBinary + ; + +valueExpression + : functionIdentifier LP (functionExpressionArgument (COMMA functionExpressionArgument)*)? RP #valueFunctionExpression + | operatorExpression #valueExpressionDefault + | left=operatorExpression comparisonOperator right=operatorExpression #comparison + ; + +operatorExpression + : primaryExpression #operatorExpressionDefault + | operator=(MINUS | PLUS) operatorExpression #arithmeticUnary + | left=operatorExpression operator=(ASTERISK | SLASH | PERCENT) right=operatorExpression #arithmeticBinary + | left=operatorExpression operator=(PLUS | MINUS) right=operatorExpression #arithmeticBinary + ; + +primaryExpression + : constant #constantDefault + | qualifiedName #dereference + | LP booleanExpression RP #parenthesizedExpression + | identifier LP (booleanExpression (COMMA booleanExpression)*)? RP #functionExpression + ; + +rowCommand + : ROW fields + ; + +fields + : field (COMMA field)* + ; + +field + : qualifiedName ASSIGN valueExpression + | booleanExpression + | qualifiedName ASSIGN booleanExpression + ; + +fromCommand + : FROM sourceIdentifier (COMMA sourceIdentifier)* + ; + +evalCommand + : EVAL fields + ; + +statsCommand + : STATS fields (BY qualifiedNames)? + ; + +sourceIdentifier + : SRC_UNQUOTED_IDENTIFIER + | SRC_QUOTED_IDENTIFIER + ; + +functionExpressionArgument + : qualifiedName + | string + ; + +qualifiedName + : identifier (DOT identifier)* + ; + +qualifiedNames + : qualifiedName (COMMA qualifiedName)* + ; + +identifier + : UNQUOTED_IDENTIFIER + | QUOTED_IDENTIFIER + ; + +functionIdentifier + : ROUND_FUNCTION_MATH + | AVG_FUNCTION_MATH + | SUM_FUNCTION_MATH + | MIN_FUNCTION_MATH + | MAX_FUNCTION_MATH + ; + + +constant + : NULL #nullLiteral + | number #numericLiteral + | booleanValue #booleanLiteral + | string #stringLiteral + ; + +limitCommand + : LIMIT INTEGER_LITERAL + ; + +sortCommand + : SORT orderExpression (COMMA orderExpression)* + ; + +orderExpression + : booleanExpression ordering=(ASC | DESC)? (NULLS nullOrdering=(FIRST | LAST))? + ; + +projectCommand + : PROJECT projectClause (COMMA projectClause)* + ; + +projectClause + : sourceIdentifier + | newName=sourceIdentifier ASSIGN oldName=sourceIdentifier + ; + +booleanValue + : TRUE | FALSE + ; + +number + : DECIMAL_LITERAL #decimalLiteral + | INTEGER_LITERAL #integerLiteral + ; + +string + : STRING + ; + +comparisonOperator + : EQ | NEQ | LT | LTE | GT | GTE + ; + +explainCommand + : EXPLAIN subqueryExpression + ; + +subqueryExpression + : OPENING_BRACKET query CLOSING_BRACKET + ; diff --git a/packages/kbn-monaco/src/esql/antlr/esql_parser.interp b/packages/kbn-monaco/src/esql/antlr/esql_parser.interp new file mode 100644 index 0000000000000..8355a7acb9fb0 --- /dev/null +++ b/packages/kbn-monaco/src/esql/antlr/esql_parser.interp @@ -0,0 +1,168 @@ +token literal names: +null +'eval' +'explain' +'from' +'row' +'stats' +'where' +'sort' +'limit' +'project' +null +null +null +null +null +null +null +'by' +'and' +'asc' +null +null +'desc' +'.' +'false' +'first' +'last' +'(' +'[' +']' +'not' +'null' +'nulls' +'or' +')' +'true' +'==' +'!=' +'<' +'<=' +'>' +'>=' +'+' +'-' +'*' +'/' +'%' +'round' +'avg' +'sum' +'min' +'max' +null +null +null +null +null +null +null +null +null +null +null + +token symbolic names: +null +EVAL +EXPLAIN +FROM +ROW +STATS +WHERE +SORT +LIMIT +PROJECT +LINE_COMMENT +MULTILINE_COMMENT +WS +PIPE +STRING +INTEGER_LITERAL +DECIMAL_LITERAL +BY +AND +ASC +ASSIGN +COMMA +DESC +DOT +FALSE +FIRST +LAST +LP +OPENING_BRACKET +CLOSING_BRACKET +NOT +NULL +NULLS +OR +RP +TRUE +EQ +NEQ +LT +LTE +GT +GTE +PLUS +MINUS +ASTERISK +SLASH +PERCENT +ROUND_FUNCTION_MATH +AVG_FUNCTION_MATH +SUM_FUNCTION_MATH +MIN_FUNCTION_MATH +MAX_FUNCTION_MATH +UNQUOTED_IDENTIFIER +QUOTED_IDENTIFIER +EXPR_LINE_COMMENT +EXPR_MULTILINE_COMMENT +EXPR_WS +SRC_UNQUOTED_IDENTIFIER +SRC_QUOTED_IDENTIFIER +SRC_LINE_COMMENT +SRC_MULTILINE_COMMENT +SRC_WS +UNKNOWN_CMD + +rule names: +singleStatement +query +sourceCommand +processingCommand +whereCommand +booleanExpression +valueExpression +operatorExpression +primaryExpression +rowCommand +fields +field +fromCommand +evalCommand +statsCommand +sourceIdentifier +functionExpressionArgument +qualifiedName +qualifiedNames +identifier +functionIdentifier +constant +limitCommand +sortCommand +orderExpression +projectCommand +projectClause +booleanValue +number +string +comparisonOperator +explainCommand +subqueryExpression + + +atn: +[3, 51485, 51898, 1421, 44986, 20307, 1543, 60043, 49729, 3, 64, 301, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 78, 10, 3, 12, 3, 14, 3, 81, 11, 3, 3, 4, 3, 4, 3, 4, 5, 4, 86, 10, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 3, 5, 5, 5, 94, 10, 5, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3, 7, 5, 7, 103, 10, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 7, 7, 111, 10, 7, 12, 7, 14, 7, 114, 11, 7, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 7, 8, 121, 10, 8, 12, 8, 14, 8, 124, 11, 8, 5, 8, 126, 10, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 5, 8, 135, 10, 8, 3, 9, 3, 9, 3, 9, 3, 9, 5, 9, 141, 10, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 3, 9, 7, 9, 149, 10, 9, 12, 9, 14, 9, 152, 11, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 7, 10, 165, 10, 10, 12, 10, 14, 10, 168, 11, 10, 5, 10, 170, 10, 10, 3, 10, 3, 10, 5, 10, 174, 10, 10, 3, 11, 3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 7, 12, 182, 10, 12, 12, 12, 14, 12, 185, 11, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 5, 13, 196, 10, 13, 3, 14, 3, 14, 3, 14, 3, 14, 7, 14, 202, 10, 14, 12, 14, 14, 14, 205, 11, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16, 3, 16, 5, 16, 214, 10, 16, 3, 17, 3, 17, 3, 18, 3, 18, 5, 18, 220, 10, 18, 3, 19, 3, 19, 3, 19, 7, 19, 225, 10, 19, 12, 19, 14, 19, 228, 11, 19, 3, 20, 3, 20, 3, 20, 7, 20, 233, 10, 20, 12, 20, 14, 20, 236, 11, 20, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 23, 3, 23, 5, 23, 246, 10, 23, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 25, 7, 25, 255, 10, 25, 12, 25, 14, 25, 258, 11, 25, 3, 26, 3, 26, 5, 26, 262, 10, 26, 3, 26, 3, 26, 5, 26, 266, 10, 26, 3, 27, 3, 27, 3, 27, 3, 27, 7, 27, 272, 10, 27, 12, 27, 14, 27, 275, 11, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 5, 28, 282, 10, 28, 3, 29, 3, 29, 3, 30, 3, 30, 5, 30, 288, 10, 30, 3, 31, 3, 31, 3, 32, 3, 32, 3, 33, 3, 33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 34, 3, 34, 2, 2, 5, 4, 12, 16, 35, 2, 2, 4, 2, 6, 2, 8, 2, 10, 2, 12, 2, 14, 2, 16, 2, 18, 2, 20, 2, 22, 2, 24, 2, 26, 2, 28, 2, 30, 2, 32, 2, 34, 2, 36, 2, 38, 2, 40, 2, 42, 2, 44, 2, 46, 2, 48, 2, 50, 2, 52, 2, 54, 2, 56, 2, 58, 2, 60, 2, 62, 2, 64, 2, 66, 2, 2, 11, 3, 2, 44, 45, 3, 2, 46, 48, 3, 2, 59, 60, 3, 2, 54, 55, 3, 2, 49, 53, 4, 2, 21, 21, 24, 24, 3, 2, 27, 28, 4, 2, 26, 26, 37, 37, 3, 2, 38, 43, 2, 307, 2, 68, 3, 2, 2, 2, 4, 71, 3, 2, 2, 2, 6, 85, 3, 2, 2, 2, 8, 93, 3, 2, 2, 2, 10, 95, 3, 2, 2, 2, 12, 102, 3, 2, 2, 2, 14, 134, 3, 2, 2, 2, 16, 140, 3, 2, 2, 2, 18, 173, 3, 2, 2, 2, 20, 175, 3, 2, 2, 2, 22, 178, 3, 2, 2, 2, 24, 195, 3, 2, 2, 2, 26, 197, 3, 2, 2, 2, 28, 206, 3, 2, 2, 2, 30, 209, 3, 2, 2, 2, 32, 215, 3, 2, 2, 2, 34, 219, 3, 2, 2, 2, 36, 221, 3, 2, 2, 2, 38, 229, 3, 2, 2, 2, 40, 237, 3, 2, 2, 2, 42, 239, 3, 2, 2, 2, 44, 245, 3, 2, 2, 2, 46, 247, 3, 2, 2, 2, 48, 250, 3, 2, 2, 2, 50, 259, 3, 2, 2, 2, 52, 267, 3, 2, 2, 2, 54, 281, 3, 2, 2, 2, 56, 283, 3, 2, 2, 2, 58, 287, 3, 2, 2, 2, 60, 289, 3, 2, 2, 2, 62, 291, 3, 2, 2, 2, 64, 293, 3, 2, 2, 2, 66, 296, 3, 2, 2, 2, 68, 69, 5, 4, 3, 2, 69, 70, 7, 2, 2, 3, 70, 3, 3, 2, 2, 2, 71, 72, 8, 3, 1, 2, 72, 73, 5, 6, 4, 2, 73, 79, 3, 2, 2, 2, 74, 75, 12, 3, 2, 2, 75, 76, 7, 15, 2, 2, 76, 78, 5, 8, 5, 2, 77, 74, 3, 2, 2, 2, 78, 81, 3, 2, 2, 2, 79, 77, 3, 2, 2, 2, 79, 80, 3, 2, 2, 2, 80, 5, 3, 2, 2, 2, 81, 79, 3, 2, 2, 2, 82, 86, 5, 64, 33, 2, 83, 86, 5, 26, 14, 2, 84, 86, 5, 20, 11, 2, 85, 82, 3, 2, 2, 2, 85, 83, 3, 2, 2, 2, 85, 84, 3, 2, 2, 2, 86, 7, 3, 2, 2, 2, 87, 94, 5, 28, 15, 2, 88, 94, 5, 46, 24, 2, 89, 94, 5, 52, 27, 2, 90, 94, 5, 48, 25, 2, 91, 94, 5, 30, 16, 2, 92, 94, 5, 10, 6, 2, 93, 87, 3, 2, 2, 2, 93, 88, 3, 2, 2, 2, 93, 89, 3, 2, 2, 2, 93, 90, 3, 2, 2, 2, 93, 91, 3, 2, 2, 2, 93, 92, 3, 2, 2, 2, 94, 9, 3, 2, 2, 2, 95, 96, 7, 8, 2, 2, 96, 97, 5, 12, 7, 2, 97, 11, 3, 2, 2, 2, 98, 99, 8, 7, 1, 2, 99, 100, 7, 32, 2, 2, 100, 103, 5, 12, 7, 6, 101, 103, 5, 14, 8, 2, 102, 98, 3, 2, 2, 2, 102, 101, 3, 2, 2, 2, 103, 112, 3, 2, 2, 2, 104, 105, 12, 4, 2, 2, 105, 106, 7, 20, 2, 2, 106, 111, 5, 12, 7, 5, 107, 108, 12, 3, 2, 2, 108, 109, 7, 35, 2, 2, 109, 111, 5, 12, 7, 4, 110, 104, 3, 2, 2, 2, 110, 107, 3, 2, 2, 2, 111, 114, 3, 2, 2, 2, 112, 110, 3, 2, 2, 2, 112, 113, 3, 2, 2, 2, 113, 13, 3, 2, 2, 2, 114, 112, 3, 2, 2, 2, 115, 116, 5, 42, 22, 2, 116, 125, 7, 29, 2, 2, 117, 122, 5, 34, 18, 2, 118, 119, 7, 23, 2, 2, 119, 121, 5, 34, 18, 2, 120, 118, 3, 2, 2, 2, 121, 124, 3, 2, 2, 2, 122, 120, 3, 2, 2, 2, 122, 123, 3, 2, 2, 2, 123, 126, 3, 2, 2, 2, 124, 122, 3, 2, 2, 2, 125, 117, 3, 2, 2, 2, 125, 126, 3, 2, 2, 2, 126, 127, 3, 2, 2, 2, 127, 128, 7, 36, 2, 2, 128, 135, 3, 2, 2, 2, 129, 135, 5, 16, 9, 2, 130, 131, 5, 16, 9, 2, 131, 132, 5, 62, 32, 2, 132, 133, 5, 16, 9, 2, 133, 135, 3, 2, 2, 2, 134, 115, 3, 2, 2, 2, 134, 129, 3, 2, 2, 2, 134, 130, 3, 2, 2, 2, 135, 15, 3, 2, 2, 2, 136, 137, 8, 9, 1, 2, 137, 141, 5, 18, 10, 2, 138, 139, 9, 2, 2, 2, 139, 141, 5, 16, 9, 5, 140, 136, 3, 2, 2, 2, 140, 138, 3, 2, 2, 2, 141, 150, 3, 2, 2, 2, 142, 143, 12, 4, 2, 2, 143, 144, 9, 3, 2, 2, 144, 149, 5, 16, 9, 5, 145, 146, 12, 3, 2, 2, 146, 147, 9, 2, 2, 2, 147, 149, 5, 16, 9, 4, 148, 142, 3, 2, 2, 2, 148, 145, 3, 2, 2, 2, 149, 152, 3, 2, 2, 2, 150, 148, 3, 2, 2, 2, 150, 151, 3, 2, 2, 2, 151, 17, 3, 2, 2, 2, 152, 150, 3, 2, 2, 2, 153, 174, 5, 44, 23, 2, 154, 174, 5, 36, 19, 2, 155, 156, 7, 29, 2, 2, 156, 157, 5, 12, 7, 2, 157, 158, 7, 36, 2, 2, 158, 174, 3, 2, 2, 2, 159, 160, 5, 40, 21, 2, 160, 169, 7, 29, 2, 2, 161, 166, 5, 12, 7, 2, 162, 163, 7, 23, 2, 2, 163, 165, 5, 12, 7, 2, 164, 162, 3, 2, 2, 2, 165, 168, 3, 2, 2, 2, 166, 164, 3, 2, 2, 2, 166, 167, 3, 2, 2, 2, 167, 170, 3, 2, 2, 2, 168, 166, 3, 2, 2, 2, 169, 161, 3, 2, 2, 2, 169, 170, 3, 2, 2, 2, 170, 171, 3, 2, 2, 2, 171, 172, 7, 36, 2, 2, 172, 174, 3, 2, 2, 2, 173, 153, 3, 2, 2, 2, 173, 154, 3, 2, 2, 2, 173, 155, 3, 2, 2, 2, 173, 159, 3, 2, 2, 2, 174, 19, 3, 2, 2, 2, 175, 176, 7, 6, 2, 2, 176, 177, 5, 22, 12, 2, 177, 21, 3, 2, 2, 2, 178, 183, 5, 24, 13, 2, 179, 180, 7, 23, 2, 2, 180, 182, 5, 24, 13, 2, 181, 179, 3, 2, 2, 2, 182, 185, 3, 2, 2, 2, 183, 181, 3, 2, 2, 2, 183, 184, 3, 2, 2, 2, 184, 23, 3, 2, 2, 2, 185, 183, 3, 2, 2, 2, 186, 187, 5, 36, 19, 2, 187, 188, 7, 22, 2, 2, 188, 189, 5, 14, 8, 2, 189, 196, 3, 2, 2, 2, 190, 196, 5, 12, 7, 2, 191, 192, 5, 36, 19, 2, 192, 193, 7, 22, 2, 2, 193, 194, 5, 12, 7, 2, 194, 196, 3, 2, 2, 2, 195, 186, 3, 2, 2, 2, 195, 190, 3, 2, 2, 2, 195, 191, 3, 2, 2, 2, 196, 25, 3, 2, 2, 2, 197, 198, 7, 5, 2, 2, 198, 203, 5, 32, 17, 2, 199, 200, 7, 23, 2, 2, 200, 202, 5, 32, 17, 2, 201, 199, 3, 2, 2, 2, 202, 205, 3, 2, 2, 2, 203, 201, 3, 2, 2, 2, 203, 204, 3, 2, 2, 2, 204, 27, 3, 2, 2, 2, 205, 203, 3, 2, 2, 2, 206, 207, 7, 3, 2, 2, 207, 208, 5, 22, 12, 2, 208, 29, 3, 2, 2, 2, 209, 210, 7, 7, 2, 2, 210, 213, 5, 22, 12, 2, 211, 212, 7, 19, 2, 2, 212, 214, 5, 38, 20, 2, 213, 211, 3, 2, 2, 2, 213, 214, 3, 2, 2, 2, 214, 31, 3, 2, 2, 2, 215, 216, 9, 4, 2, 2, 216, 33, 3, 2, 2, 2, 217, 220, 5, 36, 19, 2, 218, 220, 5, 60, 31, 2, 219, 217, 3, 2, 2, 2, 219, 218, 3, 2, 2, 2, 220, 35, 3, 2, 2, 2, 221, 226, 5, 40, 21, 2, 222, 223, 7, 25, 2, 2, 223, 225, 5, 40, 21, 2, 224, 222, 3, 2, 2, 2, 225, 228, 3, 2, 2, 2, 226, 224, 3, 2, 2, 2, 226, 227, 3, 2, 2, 2, 227, 37, 3, 2, 2, 2, 228, 226, 3, 2, 2, 2, 229, 234, 5, 36, 19, 2, 230, 231, 7, 23, 2, 2, 231, 233, 5, 36, 19, 2, 232, 230, 3, 2, 2, 2, 233, 236, 3, 2, 2, 2, 234, 232, 3, 2, 2, 2, 234, 235, 3, 2, 2, 2, 235, 39, 3, 2, 2, 2, 236, 234, 3, 2, 2, 2, 237, 238, 9, 5, 2, 2, 238, 41, 3, 2, 2, 2, 239, 240, 9, 6, 2, 2, 240, 43, 3, 2, 2, 2, 241, 246, 7, 33, 2, 2, 242, 246, 5, 58, 30, 2, 243, 246, 5, 56, 29, 2, 244, 246, 5, 60, 31, 2, 245, 241, 3, 2, 2, 2, 245, 242, 3, 2, 2, 2, 245, 243, 3, 2, 2, 2, 245, 244, 3, 2, 2, 2, 246, 45, 3, 2, 2, 2, 247, 248, 7, 10, 2, 2, 248, 249, 7, 17, 2, 2, 249, 47, 3, 2, 2, 2, 250, 251, 7, 9, 2, 2, 251, 256, 5, 50, 26, 2, 252, 253, 7, 23, 2, 2, 253, 255, 5, 50, 26, 2, 254, 252, 3, 2, 2, 2, 255, 258, 3, 2, 2, 2, 256, 254, 3, 2, 2, 2, 256, 257, 3, 2, 2, 2, 257, 49, 3, 2, 2, 2, 258, 256, 3, 2, 2, 2, 259, 261, 5, 12, 7, 2, 260, 262, 9, 7, 2, 2, 261, 260, 3, 2, 2, 2, 261, 262, 3, 2, 2, 2, 262, 265, 3, 2, 2, 2, 263, 264, 7, 34, 2, 2, 264, 266, 9, 8, 2, 2, 265, 263, 3, 2, 2, 2, 265, 266, 3, 2, 2, 2, 266, 51, 3, 2, 2, 2, 267, 268, 7, 11, 2, 2, 268, 273, 5, 54, 28, 2, 269, 270, 7, 23, 2, 2, 270, 272, 5, 54, 28, 2, 271, 269, 3, 2, 2, 2, 272, 275, 3, 2, 2, 2, 273, 271, 3, 2, 2, 2, 273, 274, 3, 2, 2, 2, 274, 53, 3, 2, 2, 2, 275, 273, 3, 2, 2, 2, 276, 282, 5, 32, 17, 2, 277, 278, 5, 32, 17, 2, 278, 279, 7, 22, 2, 2, 279, 280, 5, 32, 17, 2, 280, 282, 3, 2, 2, 2, 281, 276, 3, 2, 2, 2, 281, 277, 3, 2, 2, 2, 282, 55, 3, 2, 2, 2, 283, 284, 9, 9, 2, 2, 284, 57, 3, 2, 2, 2, 285, 288, 7, 18, 2, 2, 286, 288, 7, 17, 2, 2, 287, 285, 3, 2, 2, 2, 287, 286, 3, 2, 2, 2, 288, 59, 3, 2, 2, 2, 289, 290, 7, 16, 2, 2, 290, 61, 3, 2, 2, 2, 291, 292, 9, 10, 2, 2, 292, 63, 3, 2, 2, 2, 293, 294, 7, 4, 2, 2, 294, 295, 5, 66, 34, 2, 295, 65, 3, 2, 2, 2, 296, 297, 7, 30, 2, 2, 297, 298, 5, 4, 3, 2, 298, 299, 7, 31, 2, 2, 299, 67, 3, 2, 2, 2, 31, 79, 85, 93, 102, 110, 112, 122, 125, 134, 140, 148, 150, 166, 169, 173, 183, 195, 203, 213, 219, 226, 234, 245, 256, 261, 265, 273, 281, 287] \ No newline at end of file diff --git a/packages/kbn-monaco/src/esql/antlr/esql_parser.tokens b/packages/kbn-monaco/src/esql/antlr/esql_parser.tokens new file mode 100644 index 0000000000000..b39004ce4ce32 --- /dev/null +++ b/packages/kbn-monaco/src/esql/antlr/esql_parser.tokens @@ -0,0 +1,104 @@ +EVAL=1 +EXPLAIN=2 +FROM=3 +ROW=4 +STATS=5 +WHERE=6 +SORT=7 +LIMIT=8 +PROJECT=9 +LINE_COMMENT=10 +MULTILINE_COMMENT=11 +WS=12 +PIPE=13 +STRING=14 +INTEGER_LITERAL=15 +DECIMAL_LITERAL=16 +BY=17 +AND=18 +ASC=19 +ASSIGN=20 +COMMA=21 +DESC=22 +DOT=23 +FALSE=24 +FIRST=25 +LAST=26 +LP=27 +OPENING_BRACKET=28 +CLOSING_BRACKET=29 +NOT=30 +NULL=31 +NULLS=32 +OR=33 +RP=34 +TRUE=35 +EQ=36 +NEQ=37 +LT=38 +LTE=39 +GT=40 +GTE=41 +PLUS=42 +MINUS=43 +ASTERISK=44 +SLASH=45 +PERCENT=46 +ROUND_FUNCTION_MATH=47 +AVG_FUNCTION_MATH=48 +SUM_FUNCTION_MATH=49 +MIN_FUNCTION_MATH=50 +MAX_FUNCTION_MATH=51 +UNQUOTED_IDENTIFIER=52 +QUOTED_IDENTIFIER=53 +EXPR_LINE_COMMENT=54 +EXPR_MULTILINE_COMMENT=55 +EXPR_WS=56 +SRC_UNQUOTED_IDENTIFIER=57 +SRC_QUOTED_IDENTIFIER=58 +SRC_LINE_COMMENT=59 +SRC_MULTILINE_COMMENT=60 +SRC_WS=61 +UNKNOWN_CMD=62 +'eval'=1 +'explain'=2 +'from'=3 +'row'=4 +'stats'=5 +'where'=6 +'sort'=7 +'limit'=8 +'project'=9 +'by'=17 +'and'=18 +'asc'=19 +'desc'=22 +'.'=23 +'false'=24 +'first'=25 +'last'=26 +'('=27 +'['=28 +']'=29 +'not'=30 +'null'=31 +'nulls'=32 +'or'=33 +')'=34 +'true'=35 +'=='=36 +'!='=37 +'<'=38 +'<='=39 +'>'=40 +'>='=41 +'+'=42 +'-'=43 +'*'=44 +'/'=45 +'%'=46 +'round'=47 +'avg'=48 +'sum'=49 +'min'=50 +'max'=51 diff --git a/packages/kbn-monaco/src/esql/antlr/esql_parser.ts b/packages/kbn-monaco/src/esql/antlr/esql_parser.ts new file mode 100644 index 0000000000000..e913ccde441b1 --- /dev/null +++ b/packages/kbn-monaco/src/esql/antlr/esql_parser.ts @@ -0,0 +1,3414 @@ +// @ts-nocheck +// Generated from src/esql/antlr/esql_parser.g4 by ANTLR 4.7.3-SNAPSHOT + + +import { ATN } from "antlr4ts/atn/ATN"; +import { ATNDeserializer } from "antlr4ts/atn/ATNDeserializer"; +import { FailedPredicateException } from "antlr4ts/FailedPredicateException"; +import { NotNull } from "antlr4ts/Decorators"; +import { NoViableAltException } from "antlr4ts/NoViableAltException"; +import { Override } from "antlr4ts/Decorators"; +import { Parser } from "antlr4ts/Parser"; +import { ParserRuleContext } from "antlr4ts/ParserRuleContext"; +import { ParserATNSimulator } from "antlr4ts/atn/ParserATNSimulator"; +import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; +import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor"; +import { RecognitionException } from "antlr4ts/RecognitionException"; +import { RuleContext } from "antlr4ts/RuleContext"; +//import { RuleVersion } from "antlr4ts/RuleVersion"; +import { TerminalNode } from "antlr4ts/tree/TerminalNode"; +import { Token } from "antlr4ts/Token"; +import { TokenStream } from "antlr4ts/TokenStream"; +import { Vocabulary } from "antlr4ts/Vocabulary"; +import { VocabularyImpl } from "antlr4ts/VocabularyImpl"; + +import * as Utils from "antlr4ts/misc/Utils"; + +import { esql_parserListener } from "./esql_parser_listener"; + +export class esql_parser extends Parser { + public static readonly EVAL = 1; + public static readonly EXPLAIN = 2; + public static readonly FROM = 3; + public static readonly ROW = 4; + public static readonly STATS = 5; + public static readonly WHERE = 6; + public static readonly SORT = 7; + public static readonly LIMIT = 8; + public static readonly PROJECT = 9; + public static readonly LINE_COMMENT = 10; + public static readonly MULTILINE_COMMENT = 11; + public static readonly WS = 12; + public static readonly PIPE = 13; + public static readonly STRING = 14; + public static readonly INTEGER_LITERAL = 15; + public static readonly DECIMAL_LITERAL = 16; + public static readonly BY = 17; + public static readonly AND = 18; + public static readonly ASC = 19; + public static readonly ASSIGN = 20; + public static readonly COMMA = 21; + public static readonly DESC = 22; + public static readonly DOT = 23; + public static readonly FALSE = 24; + public static readonly FIRST = 25; + public static readonly LAST = 26; + public static readonly LP = 27; + public static readonly OPENING_BRACKET = 28; + public static readonly CLOSING_BRACKET = 29; + public static readonly NOT = 30; + public static readonly NULL = 31; + public static readonly NULLS = 32; + public static readonly OR = 33; + public static readonly RP = 34; + public static readonly TRUE = 35; + public static readonly EQ = 36; + public static readonly NEQ = 37; + public static readonly LT = 38; + public static readonly LTE = 39; + public static readonly GT = 40; + public static readonly GTE = 41; + public static readonly PLUS = 42; + public static readonly MINUS = 43; + public static readonly ASTERISK = 44; + public static readonly SLASH = 45; + public static readonly PERCENT = 46; + public static readonly ROUND_FUNCTION_MATH = 47; + public static readonly AVG_FUNCTION_MATH = 48; + public static readonly SUM_FUNCTION_MATH = 49; + public static readonly MIN_FUNCTION_MATH = 50; + public static readonly MAX_FUNCTION_MATH = 51; + public static readonly UNQUOTED_IDENTIFIER = 52; + public static readonly QUOTED_IDENTIFIER = 53; + public static readonly EXPR_LINE_COMMENT = 54; + public static readonly EXPR_MULTILINE_COMMENT = 55; + public static readonly EXPR_WS = 56; + public static readonly SRC_UNQUOTED_IDENTIFIER = 57; + public static readonly SRC_QUOTED_IDENTIFIER = 58; + public static readonly SRC_LINE_COMMENT = 59; + public static readonly SRC_MULTILINE_COMMENT = 60; + public static readonly SRC_WS = 61; + public static readonly UNKNOWN_CMD = 62; + public static readonly RULE_singleStatement = 0; + public static readonly RULE_query = 1; + public static readonly RULE_sourceCommand = 2; + public static readonly RULE_processingCommand = 3; + public static readonly RULE_whereCommand = 4; + public static readonly RULE_booleanExpression = 5; + public static readonly RULE_valueExpression = 6; + public static readonly RULE_operatorExpression = 7; + public static readonly RULE_primaryExpression = 8; + public static readonly RULE_rowCommand = 9; + public static readonly RULE_fields = 10; + public static readonly RULE_field = 11; + public static readonly RULE_fromCommand = 12; + public static readonly RULE_evalCommand = 13; + public static readonly RULE_statsCommand = 14; + public static readonly RULE_sourceIdentifier = 15; + public static readonly RULE_functionExpressionArgument = 16; + public static readonly RULE_qualifiedName = 17; + public static readonly RULE_qualifiedNames = 18; + public static readonly RULE_identifier = 19; + public static readonly RULE_functionIdentifier = 20; + public static readonly RULE_constant = 21; + public static readonly RULE_limitCommand = 22; + public static readonly RULE_sortCommand = 23; + public static readonly RULE_orderExpression = 24; + public static readonly RULE_projectCommand = 25; + public static readonly RULE_projectClause = 26; + public static readonly RULE_booleanValue = 27; + public static readonly RULE_number = 28; + public static readonly RULE_string = 29; + public static readonly RULE_comparisonOperator = 30; + public static readonly RULE_explainCommand = 31; + public static readonly RULE_subqueryExpression = 32; + // tslint:disable:no-trailing-whitespace + public static readonly ruleNames: string[] = [ + "singleStatement", "query", "sourceCommand", "processingCommand", "whereCommand", + "booleanExpression", "valueExpression", "operatorExpression", "primaryExpression", + "rowCommand", "fields", "field", "fromCommand", "evalCommand", "statsCommand", + "sourceIdentifier", "functionExpressionArgument", "qualifiedName", "qualifiedNames", + "identifier", "functionIdentifier", "constant", "limitCommand", "sortCommand", + "orderExpression", "projectCommand", "projectClause", "booleanValue", + "number", "string", "comparisonOperator", "explainCommand", "subqueryExpression", + ]; + + private static readonly _LITERAL_NAMES: Array = [ + undefined, "'eval'", "'explain'", "'from'", "'row'", "'stats'", "'where'", + "'sort'", "'limit'", "'project'", undefined, undefined, undefined, undefined, + undefined, undefined, undefined, "'by'", "'and'", "'asc'", undefined, + undefined, "'desc'", "'.'", "'false'", "'first'", "'last'", "'('", "'['", + "']'", "'not'", "'null'", "'nulls'", "'or'", "')'", "'true'", "'=='", + "'!='", "'<'", "'<='", "'>'", "'>='", "'+'", "'-'", "'*'", "'/'", "'%'", + "'round'", "'avg'", "'sum'", "'min'", "'max'", + ]; + private static readonly _SYMBOLIC_NAMES: Array = [ + undefined, "EVAL", "EXPLAIN", "FROM", "ROW", "STATS", "WHERE", "SORT", + "LIMIT", "PROJECT", "LINE_COMMENT", "MULTILINE_COMMENT", "WS", "PIPE", + "STRING", "INTEGER_LITERAL", "DECIMAL_LITERAL", "BY", "AND", "ASC", "ASSIGN", + "COMMA", "DESC", "DOT", "FALSE", "FIRST", "LAST", "LP", "OPENING_BRACKET", + "CLOSING_BRACKET", "NOT", "NULL", "NULLS", "OR", "RP", "TRUE", "EQ", "NEQ", + "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", + "ROUND_FUNCTION_MATH", "AVG_FUNCTION_MATH", "SUM_FUNCTION_MATH", "MIN_FUNCTION_MATH", + "MAX_FUNCTION_MATH", "UNQUOTED_IDENTIFIER", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", + "EXPR_MULTILINE_COMMENT", "EXPR_WS", "SRC_UNQUOTED_IDENTIFIER", "SRC_QUOTED_IDENTIFIER", + "SRC_LINE_COMMENT", "SRC_MULTILINE_COMMENT", "SRC_WS", "UNKNOWN_CMD", + ]; + public static readonly VOCABULARY: Vocabulary = new VocabularyImpl(esql_parser._LITERAL_NAMES, esql_parser._SYMBOLIC_NAMES, []); + + // @Override + // @NotNull + public get vocabulary(): Vocabulary { + return esql_parser.VOCABULARY; + } + // tslint:enable:no-trailing-whitespace + + // @Override + public get grammarFileName(): string { return "esql_parser.g4"; } + + // @Override + public get ruleNames(): string[] { return esql_parser.ruleNames; } + + // @Override + public get serializedATN(): string { return esql_parser._serializedATN; } + + constructor(input: TokenStream) { + super(input); + this._interp = new ParserATNSimulator(esql_parser._ATN, this); + } + // @RuleVersion(0) + public singleStatement(): SingleStatementContext { + let _localctx: SingleStatementContext = new SingleStatementContext(this._ctx, this.state); + this.enterRule(_localctx, 0, esql_parser.RULE_singleStatement); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 66; + this.query(0); + this.state = 67; + this.match(esql_parser.EOF); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + + public query(): QueryContext; + public query(_p: number): QueryContext; + // @RuleVersion(0) + public query(_p?: number): QueryContext { + if (_p === undefined) { + _p = 0; + } + + let _parentctx: ParserRuleContext = this._ctx; + let _parentState: number = this.state; + let _localctx: QueryContext = new QueryContext(this._ctx, _parentState); + let _prevctx: QueryContext = _localctx; + let _startState: number = 2; + this.enterRecursionRule(_localctx, 2, esql_parser.RULE_query, _p); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + { + _localctx = new SingleCommandQueryContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + + this.state = 70; + this.sourceCommand(); + } + this._ctx._stop = this._input.tryLT(-1); + this.state = 77; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 0, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + if (this._parseListeners != null) { + this.triggerExitRuleEvent(); + } + _prevctx = _localctx; + { + { + _localctx = new CompositeQueryContext(new QueryContext(_parentctx, _parentState)); + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_query); + this.state = 72; + if (!(this.precpred(this._ctx, 1))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 1)"); + } + this.state = 73; + this.match(esql_parser.PIPE); + this.state = 74; + this.processingCommand(); + } + } + } + this.state = 79; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 0, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.unrollRecursionContexts(_parentctx); + } + return _localctx; + } + // @RuleVersion(0) + public sourceCommand(): SourceCommandContext { + let _localctx: SourceCommandContext = new SourceCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 4, esql_parser.RULE_sourceCommand); + try { + this.state = 83; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.EXPLAIN: + this.enterOuterAlt(_localctx, 1); + { + this.state = 80; + this.explainCommand(); + } + break; + case esql_parser.FROM: + this.enterOuterAlt(_localctx, 2); + { + this.state = 81; + this.fromCommand(); + } + break; + case esql_parser.ROW: + this.enterOuterAlt(_localctx, 3); + { + this.state = 82; + this.rowCommand(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public processingCommand(): ProcessingCommandContext { + let _localctx: ProcessingCommandContext = new ProcessingCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 6, esql_parser.RULE_processingCommand); + try { + this.state = 91; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.EVAL: + this.enterOuterAlt(_localctx, 1); + { + this.state = 85; + this.evalCommand(); + } + break; + case esql_parser.LIMIT: + this.enterOuterAlt(_localctx, 2); + { + this.state = 86; + this.limitCommand(); + } + break; + case esql_parser.PROJECT: + this.enterOuterAlt(_localctx, 3); + { + this.state = 87; + this.projectCommand(); + } + break; + case esql_parser.SORT: + this.enterOuterAlt(_localctx, 4); + { + this.state = 88; + this.sortCommand(); + } + break; + case esql_parser.STATS: + this.enterOuterAlt(_localctx, 5); + { + this.state = 89; + this.statsCommand(); + } + break; + case esql_parser.WHERE: + this.enterOuterAlt(_localctx, 6); + { + this.state = 90; + this.whereCommand(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public whereCommand(): WhereCommandContext { + let _localctx: WhereCommandContext = new WhereCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 8, esql_parser.RULE_whereCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 93; + this.match(esql_parser.WHERE); + this.state = 94; + this.booleanExpression(0); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + + public booleanExpression(): BooleanExpressionContext; + public booleanExpression(_p: number): BooleanExpressionContext; + // @RuleVersion(0) + public booleanExpression(_p?: number): BooleanExpressionContext { + if (_p === undefined) { + _p = 0; + } + + let _parentctx: ParserRuleContext = this._ctx; + let _parentState: number = this.state; + let _localctx: BooleanExpressionContext = new BooleanExpressionContext(this._ctx, _parentState); + let _prevctx: BooleanExpressionContext = _localctx; + let _startState: number = 10; + this.enterRecursionRule(_localctx, 10, esql_parser.RULE_booleanExpression, _p); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 100; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.NOT: + { + _localctx = new LogicalNotContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + + this.state = 97; + this.match(esql_parser.NOT); + this.state = 98; + this.booleanExpression(4); + } + break; + case esql_parser.STRING: + case esql_parser.INTEGER_LITERAL: + case esql_parser.DECIMAL_LITERAL: + case esql_parser.FALSE: + case esql_parser.LP: + case esql_parser.NULL: + case esql_parser.TRUE: + case esql_parser.PLUS: + case esql_parser.MINUS: + case esql_parser.ROUND_FUNCTION_MATH: + case esql_parser.AVG_FUNCTION_MATH: + case esql_parser.SUM_FUNCTION_MATH: + case esql_parser.MIN_FUNCTION_MATH: + case esql_parser.MAX_FUNCTION_MATH: + case esql_parser.UNQUOTED_IDENTIFIER: + case esql_parser.QUOTED_IDENTIFIER: + { + _localctx = new BooleanDefaultContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 99; + this.valueExpression(); + } + break; + default: + throw new NoViableAltException(this); + } + this._ctx._stop = this._input.tryLT(-1); + this.state = 110; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 5, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + if (this._parseListeners != null) { + this.triggerExitRuleEvent(); + } + _prevctx = _localctx; + { + this.state = 108; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 4, this._ctx) ) { + case 1: + { + _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState)); + (_localctx as LogicalBinaryContext)._left = _prevctx; + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_booleanExpression); + this.state = 102; + if (!(this.precpred(this._ctx, 2))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 2)"); + } + this.state = 103; + (_localctx as LogicalBinaryContext)._operator = this.match(esql_parser.AND); + this.state = 104; + (_localctx as LogicalBinaryContext)._right = this.booleanExpression(3); + } + break; + + case 2: + { + _localctx = new LogicalBinaryContext(new BooleanExpressionContext(_parentctx, _parentState)); + (_localctx as LogicalBinaryContext)._left = _prevctx; + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_booleanExpression); + this.state = 105; + if (!(this.precpred(this._ctx, 1))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 1)"); + } + this.state = 106; + (_localctx as LogicalBinaryContext)._operator = this.match(esql_parser.OR); + this.state = 107; + (_localctx as LogicalBinaryContext)._right = this.booleanExpression(2); + } + break; + } + } + } + this.state = 112; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 5, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.unrollRecursionContexts(_parentctx); + } + return _localctx; + } + // @RuleVersion(0) + public valueExpression(): ValueExpressionContext { + let _localctx: ValueExpressionContext = new ValueExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 12, esql_parser.RULE_valueExpression); + let _la: number; + try { + this.state = 132; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 8, this._ctx) ) { + case 1: + _localctx = new ValueFunctionExpressionContext(_localctx); + this.enterOuterAlt(_localctx, 1); + { + this.state = 113; + this.functionIdentifier(); + this.state = 114; + this.match(esql_parser.LP); + this.state = 123; + this._errHandler.sync(this); + _la = this._input.LA(1); + if (_la === esql_parser.STRING || _la === esql_parser.UNQUOTED_IDENTIFIER || _la === esql_parser.QUOTED_IDENTIFIER) { + { + this.state = 115; + this.functionExpressionArgument(); + this.state = 120; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { + { + { + this.state = 116; + this.match(esql_parser.COMMA); + this.state = 117; + this.functionExpressionArgument(); + } + } + this.state = 122; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + } + } + + this.state = 125; + this.match(esql_parser.RP); + } + break; + + case 2: + _localctx = new ValueExpressionDefaultContext(_localctx); + this.enterOuterAlt(_localctx, 2); + { + this.state = 127; + this.operatorExpression(0); + } + break; + + case 3: + _localctx = new ComparisonContext(_localctx); + this.enterOuterAlt(_localctx, 3); + { + this.state = 128; + (_localctx as ComparisonContext)._left = this.operatorExpression(0); + this.state = 129; + this.comparisonOperator(); + this.state = 130; + (_localctx as ComparisonContext)._right = this.operatorExpression(0); + } + break; + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + + public operatorExpression(): OperatorExpressionContext; + public operatorExpression(_p: number): OperatorExpressionContext; + // @RuleVersion(0) + public operatorExpression(_p?: number): OperatorExpressionContext { + if (_p === undefined) { + _p = 0; + } + + let _parentctx: ParserRuleContext = this._ctx; + let _parentState: number = this.state; + let _localctx: OperatorExpressionContext = new OperatorExpressionContext(this._ctx, _parentState); + let _prevctx: OperatorExpressionContext = _localctx; + let _startState: number = 14; + this.enterRecursionRule(_localctx, 14, esql_parser.RULE_operatorExpression, _p); + let _la: number; + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 138; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.STRING: + case esql_parser.INTEGER_LITERAL: + case esql_parser.DECIMAL_LITERAL: + case esql_parser.FALSE: + case esql_parser.LP: + case esql_parser.NULL: + case esql_parser.TRUE: + case esql_parser.UNQUOTED_IDENTIFIER: + case esql_parser.QUOTED_IDENTIFIER: + { + _localctx = new OperatorExpressionDefaultContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + + this.state = 135; + this.primaryExpression(); + } + break; + case esql_parser.PLUS: + case esql_parser.MINUS: + { + _localctx = new ArithmeticUnaryContext(_localctx); + this._ctx = _localctx; + _prevctx = _localctx; + this.state = 136; + (_localctx as ArithmeticUnaryContext)._operator = this._input.LT(1); + _la = this._input.LA(1); + if (!(_la === esql_parser.PLUS || _la === esql_parser.MINUS)) { + (_localctx as ArithmeticUnaryContext)._operator = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 137; + this.operatorExpression(3); + } + break; + default: + throw new NoViableAltException(this); + } + this._ctx._stop = this._input.tryLT(-1); + this.state = 148; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 11, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + if (this._parseListeners != null) { + this.triggerExitRuleEvent(); + } + _prevctx = _localctx; + { + this.state = 146; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 10, this._ctx) ) { + case 1: + { + _localctx = new ArithmeticBinaryContext(new OperatorExpressionContext(_parentctx, _parentState)); + (_localctx as ArithmeticBinaryContext)._left = _prevctx; + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_operatorExpression); + this.state = 140; + if (!(this.precpred(this._ctx, 2))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 2)"); + } + this.state = 141; + (_localctx as ArithmeticBinaryContext)._operator = this._input.LT(1); + _la = this._input.LA(1); + if (!(((((_la - 44)) & ~0x1F) === 0 && ((1 << (_la - 44)) & ((1 << (esql_parser.ASTERISK - 44)) | (1 << (esql_parser.SLASH - 44)) | (1 << (esql_parser.PERCENT - 44)))) !== 0))) { + (_localctx as ArithmeticBinaryContext)._operator = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 142; + (_localctx as ArithmeticBinaryContext)._right = this.operatorExpression(3); + } + break; + + case 2: + { + _localctx = new ArithmeticBinaryContext(new OperatorExpressionContext(_parentctx, _parentState)); + (_localctx as ArithmeticBinaryContext)._left = _prevctx; + this.pushNewRecursionContext(_localctx, _startState, esql_parser.RULE_operatorExpression); + this.state = 143; + if (!(this.precpred(this._ctx, 1))) { + throw new FailedPredicateException(this, "this.precpred(this._ctx, 1)"); + } + this.state = 144; + (_localctx as ArithmeticBinaryContext)._operator = this._input.LT(1); + _la = this._input.LA(1); + if (!(_la === esql_parser.PLUS || _la === esql_parser.MINUS)) { + (_localctx as ArithmeticBinaryContext)._operator = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + this.state = 145; + (_localctx as ArithmeticBinaryContext)._right = this.operatorExpression(2); + } + break; + } + } + } + this.state = 150; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 11, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.unrollRecursionContexts(_parentctx); + } + return _localctx; + } + // @RuleVersion(0) + public primaryExpression(): PrimaryExpressionContext { + let _localctx: PrimaryExpressionContext = new PrimaryExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 16, esql_parser.RULE_primaryExpression); + let _la: number; + try { + this.state = 171; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 14, this._ctx) ) { + case 1: + _localctx = new ConstantDefaultContext(_localctx); + this.enterOuterAlt(_localctx, 1); + { + this.state = 151; + this.constant(); + } + break; + + case 2: + _localctx = new DereferenceContext(_localctx); + this.enterOuterAlt(_localctx, 2); + { + this.state = 152; + this.qualifiedName(); + } + break; + + case 3: + _localctx = new ParenthesizedExpressionContext(_localctx); + this.enterOuterAlt(_localctx, 3); + { + this.state = 153; + this.match(esql_parser.LP); + this.state = 154; + this.booleanExpression(0); + this.state = 155; + this.match(esql_parser.RP); + } + break; + + case 4: + _localctx = new FunctionExpressionContext(_localctx); + this.enterOuterAlt(_localctx, 4); + { + this.state = 157; + this.identifier(); + this.state = 158; + this.match(esql_parser.LP); + this.state = 167; + this._errHandler.sync(this); + _la = this._input.LA(1); + if ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << esql_parser.STRING) | (1 << esql_parser.INTEGER_LITERAL) | (1 << esql_parser.DECIMAL_LITERAL) | (1 << esql_parser.FALSE) | (1 << esql_parser.LP) | (1 << esql_parser.NOT) | (1 << esql_parser.NULL))) !== 0) || ((((_la - 35)) & ~0x1F) === 0 && ((1 << (_la - 35)) & ((1 << (esql_parser.TRUE - 35)) | (1 << (esql_parser.PLUS - 35)) | (1 << (esql_parser.MINUS - 35)) | (1 << (esql_parser.ROUND_FUNCTION_MATH - 35)) | (1 << (esql_parser.AVG_FUNCTION_MATH - 35)) | (1 << (esql_parser.SUM_FUNCTION_MATH - 35)) | (1 << (esql_parser.MIN_FUNCTION_MATH - 35)) | (1 << (esql_parser.MAX_FUNCTION_MATH - 35)) | (1 << (esql_parser.UNQUOTED_IDENTIFIER - 35)) | (1 << (esql_parser.QUOTED_IDENTIFIER - 35)))) !== 0)) { + { + this.state = 159; + this.booleanExpression(0); + this.state = 164; + this._errHandler.sync(this); + _la = this._input.LA(1); + while (_la === esql_parser.COMMA) { + { + { + this.state = 160; + this.match(esql_parser.COMMA); + this.state = 161; + this.booleanExpression(0); + } + } + this.state = 166; + this._errHandler.sync(this); + _la = this._input.LA(1); + } + } + } + + this.state = 169; + this.match(esql_parser.RP); + } + break; + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public rowCommand(): RowCommandContext { + let _localctx: RowCommandContext = new RowCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 18, esql_parser.RULE_rowCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 173; + this.match(esql_parser.ROW); + this.state = 174; + this.fields(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public fields(): FieldsContext { + let _localctx: FieldsContext = new FieldsContext(this._ctx, this.state); + this.enterRule(_localctx, 20, esql_parser.RULE_fields); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 176; + this.field(); + this.state = 181; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 15, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 177; + this.match(esql_parser.COMMA); + this.state = 178; + this.field(); + } + } + } + this.state = 183; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 15, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public field(): FieldContext { + let _localctx: FieldContext = new FieldContext(this._ctx, this.state); + this.enterRule(_localctx, 22, esql_parser.RULE_field); + try { + this.state = 193; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 16, this._ctx) ) { + case 1: + this.enterOuterAlt(_localctx, 1); + { + this.state = 184; + this.qualifiedName(); + this.state = 185; + this.match(esql_parser.ASSIGN); + this.state = 186; + this.valueExpression(); + } + break; + + case 2: + this.enterOuterAlt(_localctx, 2); + { + this.state = 188; + this.booleanExpression(0); + } + break; + + case 3: + this.enterOuterAlt(_localctx, 3); + { + this.state = 189; + this.qualifiedName(); + this.state = 190; + this.match(esql_parser.ASSIGN); + this.state = 191; + this.booleanExpression(0); + } + break; + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public fromCommand(): FromCommandContext { + let _localctx: FromCommandContext = new FromCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 24, esql_parser.RULE_fromCommand); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 195; + this.match(esql_parser.FROM); + this.state = 196; + this.sourceIdentifier(); + this.state = 201; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 17, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 197; + this.match(esql_parser.COMMA); + this.state = 198; + this.sourceIdentifier(); + } + } + } + this.state = 203; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 17, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public evalCommand(): EvalCommandContext { + let _localctx: EvalCommandContext = new EvalCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 26, esql_parser.RULE_evalCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 204; + this.match(esql_parser.EVAL); + this.state = 205; + this.fields(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public statsCommand(): StatsCommandContext { + let _localctx: StatsCommandContext = new StatsCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 28, esql_parser.RULE_statsCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 207; + this.match(esql_parser.STATS); + this.state = 208; + this.fields(); + this.state = 211; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 18, this._ctx) ) { + case 1: + { + this.state = 209; + this.match(esql_parser.BY); + this.state = 210; + this.qualifiedNames(); + } + break; + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public sourceIdentifier(): SourceIdentifierContext { + let _localctx: SourceIdentifierContext = new SourceIdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 30, esql_parser.RULE_sourceIdentifier); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 213; + _la = this._input.LA(1); + if (!(_la === esql_parser.SRC_UNQUOTED_IDENTIFIER || _la === esql_parser.SRC_QUOTED_IDENTIFIER)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public functionExpressionArgument(): FunctionExpressionArgumentContext { + let _localctx: FunctionExpressionArgumentContext = new FunctionExpressionArgumentContext(this._ctx, this.state); + this.enterRule(_localctx, 32, esql_parser.RULE_functionExpressionArgument); + try { + this.state = 217; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.UNQUOTED_IDENTIFIER: + case esql_parser.QUOTED_IDENTIFIER: + this.enterOuterAlt(_localctx, 1); + { + this.state = 215; + this.qualifiedName(); + } + break; + case esql_parser.STRING: + this.enterOuterAlt(_localctx, 2); + { + this.state = 216; + this.string(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public qualifiedName(): QualifiedNameContext { + let _localctx: QualifiedNameContext = new QualifiedNameContext(this._ctx, this.state); + this.enterRule(_localctx, 34, esql_parser.RULE_qualifiedName); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 219; + this.identifier(); + this.state = 224; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 20, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 220; + this.match(esql_parser.DOT); + this.state = 221; + this.identifier(); + } + } + } + this.state = 226; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 20, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public qualifiedNames(): QualifiedNamesContext { + let _localctx: QualifiedNamesContext = new QualifiedNamesContext(this._ctx, this.state); + this.enterRule(_localctx, 36, esql_parser.RULE_qualifiedNames); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 227; + this.qualifiedName(); + this.state = 232; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 21, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 228; + this.match(esql_parser.COMMA); + this.state = 229; + this.qualifiedName(); + } + } + } + this.state = 234; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 21, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public identifier(): IdentifierContext { + let _localctx: IdentifierContext = new IdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 38, esql_parser.RULE_identifier); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 235; + _la = this._input.LA(1); + if (!(_la === esql_parser.UNQUOTED_IDENTIFIER || _la === esql_parser.QUOTED_IDENTIFIER)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public functionIdentifier(): FunctionIdentifierContext { + let _localctx: FunctionIdentifierContext = new FunctionIdentifierContext(this._ctx, this.state); + this.enterRule(_localctx, 40, esql_parser.RULE_functionIdentifier); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 237; + _la = this._input.LA(1); + if (!(((((_la - 47)) & ~0x1F) === 0 && ((1 << (_la - 47)) & ((1 << (esql_parser.ROUND_FUNCTION_MATH - 47)) | (1 << (esql_parser.AVG_FUNCTION_MATH - 47)) | (1 << (esql_parser.SUM_FUNCTION_MATH - 47)) | (1 << (esql_parser.MIN_FUNCTION_MATH - 47)) | (1 << (esql_parser.MAX_FUNCTION_MATH - 47)))) !== 0))) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public constant(): ConstantContext { + let _localctx: ConstantContext = new ConstantContext(this._ctx, this.state); + this.enterRule(_localctx, 42, esql_parser.RULE_constant); + try { + this.state = 243; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.NULL: + _localctx = new NullLiteralContext(_localctx); + this.enterOuterAlt(_localctx, 1); + { + this.state = 239; + this.match(esql_parser.NULL); + } + break; + case esql_parser.INTEGER_LITERAL: + case esql_parser.DECIMAL_LITERAL: + _localctx = new NumericLiteralContext(_localctx); + this.enterOuterAlt(_localctx, 2); + { + this.state = 240; + this.number(); + } + break; + case esql_parser.FALSE: + case esql_parser.TRUE: + _localctx = new BooleanLiteralContext(_localctx); + this.enterOuterAlt(_localctx, 3); + { + this.state = 241; + this.booleanValue(); + } + break; + case esql_parser.STRING: + _localctx = new StringLiteralContext(_localctx); + this.enterOuterAlt(_localctx, 4); + { + this.state = 242; + this.string(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public limitCommand(): LimitCommandContext { + let _localctx: LimitCommandContext = new LimitCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 44, esql_parser.RULE_limitCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 245; + this.match(esql_parser.LIMIT); + this.state = 246; + this.match(esql_parser.INTEGER_LITERAL); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public sortCommand(): SortCommandContext { + let _localctx: SortCommandContext = new SortCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 46, esql_parser.RULE_sortCommand); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 248; + this.match(esql_parser.SORT); + this.state = 249; + this.orderExpression(); + this.state = 254; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 23, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 250; + this.match(esql_parser.COMMA); + this.state = 251; + this.orderExpression(); + } + } + } + this.state = 256; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 23, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public orderExpression(): OrderExpressionContext { + let _localctx: OrderExpressionContext = new OrderExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 48, esql_parser.RULE_orderExpression); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 257; + this.booleanExpression(0); + this.state = 259; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 24, this._ctx) ) { + case 1: + { + this.state = 258; + _localctx._ordering = this._input.LT(1); + _la = this._input.LA(1); + if (!(_la === esql_parser.ASC || _la === esql_parser.DESC)) { + _localctx._ordering = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + break; + } + this.state = 263; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 25, this._ctx) ) { + case 1: + { + this.state = 261; + this.match(esql_parser.NULLS); + this.state = 262; + _localctx._nullOrdering = this._input.LT(1); + _la = this._input.LA(1); + if (!(_la === esql_parser.FIRST || _la === esql_parser.LAST)) { + _localctx._nullOrdering = this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + break; + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public projectCommand(): ProjectCommandContext { + let _localctx: ProjectCommandContext = new ProjectCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 50, esql_parser.RULE_projectCommand); + try { + let _alt: number; + this.enterOuterAlt(_localctx, 1); + { + this.state = 265; + this.match(esql_parser.PROJECT); + this.state = 266; + this.projectClause(); + this.state = 271; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 26, this._ctx); + while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { + if (_alt === 1) { + { + { + this.state = 267; + this.match(esql_parser.COMMA); + this.state = 268; + this.projectClause(); + } + } + } + this.state = 273; + this._errHandler.sync(this); + _alt = this.interpreter.adaptivePredict(this._input, 26, this._ctx); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public projectClause(): ProjectClauseContext { + let _localctx: ProjectClauseContext = new ProjectClauseContext(this._ctx, this.state); + this.enterRule(_localctx, 52, esql_parser.RULE_projectClause); + try { + this.state = 279; + this._errHandler.sync(this); + switch ( this.interpreter.adaptivePredict(this._input, 27, this._ctx) ) { + case 1: + this.enterOuterAlt(_localctx, 1); + { + this.state = 274; + this.sourceIdentifier(); + } + break; + + case 2: + this.enterOuterAlt(_localctx, 2); + { + this.state = 275; + _localctx._newName = this.sourceIdentifier(); + this.state = 276; + this.match(esql_parser.ASSIGN); + this.state = 277; + _localctx._oldName = this.sourceIdentifier(); + } + break; + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public booleanValue(): BooleanValueContext { + let _localctx: BooleanValueContext = new BooleanValueContext(this._ctx, this.state); + this.enterRule(_localctx, 54, esql_parser.RULE_booleanValue); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 281; + _la = this._input.LA(1); + if (!(_la === esql_parser.FALSE || _la === esql_parser.TRUE)) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public number(): NumberContext { + let _localctx: NumberContext = new NumberContext(this._ctx, this.state); + this.enterRule(_localctx, 56, esql_parser.RULE_number); + try { + this.state = 285; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case esql_parser.DECIMAL_LITERAL: + _localctx = new DecimalLiteralContext(_localctx); + this.enterOuterAlt(_localctx, 1); + { + this.state = 283; + this.match(esql_parser.DECIMAL_LITERAL); + } + break; + case esql_parser.INTEGER_LITERAL: + _localctx = new IntegerLiteralContext(_localctx); + this.enterOuterAlt(_localctx, 2); + { + this.state = 284; + this.match(esql_parser.INTEGER_LITERAL); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public string(): StringContext { + let _localctx: StringContext = new StringContext(this._ctx, this.state); + this.enterRule(_localctx, 58, esql_parser.RULE_string); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 287; + this.match(esql_parser.STRING); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public comparisonOperator(): ComparisonOperatorContext { + let _localctx: ComparisonOperatorContext = new ComparisonOperatorContext(this._ctx, this.state); + this.enterRule(_localctx, 60, esql_parser.RULE_comparisonOperator); + let _la: number; + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 289; + _la = this._input.LA(1); + if (!(((((_la - 36)) & ~0x1F) === 0 && ((1 << (_la - 36)) & ((1 << (esql_parser.EQ - 36)) | (1 << (esql_parser.NEQ - 36)) | (1 << (esql_parser.LT - 36)) | (1 << (esql_parser.LTE - 36)) | (1 << (esql_parser.GT - 36)) | (1 << (esql_parser.GTE - 36)))) !== 0))) { + this._errHandler.recoverInline(this); + } else { + if (this._input.LA(1) === Token.EOF) { + this.matchedEOF = true; + } + + this._errHandler.reportMatch(this); + this.consume(); + } + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public explainCommand(): ExplainCommandContext { + let _localctx: ExplainCommandContext = new ExplainCommandContext(this._ctx, this.state); + this.enterRule(_localctx, 62, esql_parser.RULE_explainCommand); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 291; + this.match(esql_parser.EXPLAIN); + this.state = 292; + this.subqueryExpression(); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + // @RuleVersion(0) + public subqueryExpression(): SubqueryExpressionContext { + let _localctx: SubqueryExpressionContext = new SubqueryExpressionContext(this._ctx, this.state); + this.enterRule(_localctx, 64, esql_parser.RULE_subqueryExpression); + try { + this.enterOuterAlt(_localctx, 1); + { + this.state = 294; + this.match(esql_parser.OPENING_BRACKET); + this.state = 295; + this.query(0); + this.state = 296; + this.match(esql_parser.CLOSING_BRACKET); + } + } + catch (re) { + if (re instanceof RecognitionException) { + _localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return _localctx; + } + + public sempred(_localctx: RuleContext, ruleIndex: number, predIndex: number): boolean { + switch (ruleIndex) { + case 1: + return this.query_sempred(_localctx as QueryContext, predIndex); + + case 5: + return this.booleanExpression_sempred(_localctx as BooleanExpressionContext, predIndex); + + case 7: + return this.operatorExpression_sempred(_localctx as OperatorExpressionContext, predIndex); + } + return true; + } + private query_sempred(_localctx: QueryContext, predIndex: number): boolean { + switch (predIndex) { + case 0: + return this.precpred(this._ctx, 1); + } + return true; + } + private booleanExpression_sempred(_localctx: BooleanExpressionContext, predIndex: number): boolean { + switch (predIndex) { + case 1: + return this.precpred(this._ctx, 2); + + case 2: + return this.precpred(this._ctx, 1); + } + return true; + } + private operatorExpression_sempred(_localctx: OperatorExpressionContext, predIndex: number): boolean { + switch (predIndex) { + case 3: + return this.precpred(this._ctx, 2); + + case 4: + return this.precpred(this._ctx, 1); + } + return true; + } + + public static readonly _serializedATN: string = + "\x03\uC91D\uCABA\u058D\uAFBA\u4F53\u0607\uEA8B\uC241\x03@\u012D\x04\x02" + + "\t\x02\x04\x03\t\x03\x04\x04\t\x04\x04\x05\t\x05\x04\x06\t\x06\x04\x07" + + "\t\x07\x04\b\t\b\x04\t\t\t\x04\n\t\n\x04\v\t\v\x04\f\t\f\x04\r\t\r\x04" + + "\x0E\t\x0E\x04\x0F\t\x0F\x04\x10\t\x10\x04\x11\t\x11\x04\x12\t\x12\x04" + + "\x13\t\x13\x04\x14\t\x14\x04\x15\t\x15\x04\x16\t\x16\x04\x17\t\x17\x04" + + "\x18\t\x18\x04\x19\t\x19\x04\x1A\t\x1A\x04\x1B\t\x1B\x04\x1C\t\x1C\x04" + + "\x1D\t\x1D\x04\x1E\t\x1E\x04\x1F\t\x1F\x04 \t \x04!\t!\x04\"\t\"\x03\x02" + + "\x03\x02\x03\x02\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x03\x07\x03" + + "N\n\x03\f\x03\x0E\x03Q\v\x03\x03\x04\x03\x04\x03\x04\x05\x04V\n\x04\x03" + + "\x05\x03\x05\x03\x05\x03\x05\x03\x05\x03\x05\x05\x05^\n\x05\x03\x06\x03" + + "\x06\x03\x06\x03\x07\x03\x07\x03\x07\x03\x07\x05\x07g\n\x07\x03\x07\x03" + + "\x07\x03\x07\x03\x07\x03\x07\x03\x07\x07\x07o\n\x07\f\x07\x0E\x07r\v\x07" + + "\x03\b\x03\b\x03\b\x03\b\x03\b\x07\by\n\b\f\b\x0E\b|\v\b\x05\b~\n\b\x03" + + "\b\x03\b\x03\b\x03\b\x03\b\x03\b\x03\b\x05\b\x87\n\b\x03\t\x03\t\x03\t" + + "\x03\t\x05\t\x8D\n\t\x03\t\x03\t\x03\t\x03\t\x03\t\x03\t\x07\t\x95\n\t" + + "\f\t\x0E\t\x98\v\t\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03\n\x03" + + "\n\x03\n\x03\n\x07\n\xA5\n\n\f\n\x0E\n\xA8\v\n\x05\n\xAA\n\n\x03\n\x03" + + "\n\x05\n\xAE\n\n\x03\v\x03\v\x03\v\x03\f\x03\f\x03\f\x07\f\xB6\n\f\f\f" + + "\x0E\f\xB9\v\f\x03\r\x03\r\x03\r\x03\r\x03\r\x03\r\x03\r\x03\r\x03\r\x05" + + "\r\xC4\n\r\x03\x0E\x03\x0E\x03\x0E\x03\x0E\x07\x0E\xCA\n\x0E\f\x0E\x0E" + + "\x0E\xCD\v\x0E\x03\x0F\x03\x0F\x03\x0F\x03\x10\x03\x10\x03\x10\x03\x10" + + "\x05\x10\xD6\n\x10\x03\x11\x03\x11\x03\x12\x03\x12\x05\x12\xDC\n\x12\x03" + + "\x13\x03\x13\x03\x13\x07\x13\xE1\n\x13\f\x13\x0E\x13\xE4\v\x13\x03\x14" + + "\x03\x14\x03\x14\x07\x14\xE9\n\x14\f\x14\x0E\x14\xEC\v\x14\x03\x15\x03" + + "\x15\x03\x16\x03\x16\x03\x17\x03\x17\x03\x17\x03\x17\x05\x17\xF6\n\x17" + + "\x03\x18\x03\x18\x03\x18\x03\x19\x03\x19\x03\x19\x03\x19\x07\x19\xFF\n" + + "\x19\f\x19\x0E\x19\u0102\v\x19\x03\x1A\x03\x1A\x05\x1A\u0106\n\x1A\x03" + + "\x1A\x03\x1A\x05\x1A\u010A\n\x1A\x03\x1B\x03\x1B\x03\x1B\x03\x1B\x07\x1B" + + "\u0110\n\x1B\f\x1B\x0E\x1B\u0113\v\x1B\x03\x1C\x03\x1C\x03\x1C\x03\x1C" + + "\x03\x1C\x05\x1C\u011A\n\x1C\x03\x1D\x03\x1D\x03\x1E\x03\x1E\x05\x1E\u0120" + + "\n\x1E\x03\x1F\x03\x1F\x03 \x03 \x03!\x03!\x03!\x03\"\x03\"\x03\"\x03" + + "\"\x03\"\x02\x02\x05\x04\f\x10#\x02\x02\x04\x02\x06\x02\b\x02\n\x02\f" + + "\x02\x0E\x02\x10\x02\x12\x02\x14\x02\x16\x02\x18\x02\x1A\x02\x1C\x02\x1E" + + "\x02 \x02\"\x02$\x02&\x02(\x02*\x02,\x02.\x020\x022\x024\x026\x028\x02" + + ":\x02<\x02>\x02@\x02B\x02\x02\v\x03\x02,-\x03\x02.0\x03\x02;<\x03\x02" + + "67\x03\x0215\x04\x02\x15\x15\x18\x18\x03\x02\x1B\x1C\x04\x02\x1A\x1A%" + + "%\x03\x02&+\x02\u0133\x02D\x03\x02\x02\x02\x04G\x03\x02\x02\x02\x06U\x03" + + "\x02\x02\x02\b]\x03\x02\x02\x02\n_\x03\x02\x02\x02\ff\x03\x02\x02\x02" + + "\x0E\x86\x03\x02\x02\x02\x10\x8C\x03\x02\x02\x02\x12\xAD\x03\x02\x02\x02" + + "\x14\xAF\x03\x02\x02\x02\x16\xB2\x03\x02\x02\x02\x18\xC3\x03\x02\x02\x02" + + "\x1A\xC5\x03\x02\x02\x02\x1C\xCE\x03\x02\x02\x02\x1E\xD1\x03\x02\x02\x02" + + " \xD7\x03\x02\x02\x02\"\xDB\x03\x02\x02\x02$\xDD\x03\x02\x02\x02&\xE5" + + "\x03\x02\x02\x02(\xED\x03\x02\x02\x02*\xEF\x03\x02\x02\x02,\xF5\x03\x02" + + "\x02\x02.\xF7\x03\x02\x02\x020\xFA\x03\x02\x02\x022\u0103\x03\x02\x02" + + "\x024\u010B\x03\x02\x02\x026\u0119\x03\x02\x02\x028\u011B\x03\x02\x02" + + "\x02:\u011F\x03\x02\x02\x02<\u0121\x03\x02\x02\x02>\u0123\x03\x02\x02" + + "\x02@\u0125\x03\x02\x02\x02B\u0128\x03\x02\x02\x02DE\x05\x04\x03\x02E" + + "F\x07\x02\x02\x03F\x03\x03\x02\x02\x02GH\b\x03\x01\x02HI\x05\x06\x04\x02" + + "IO\x03\x02\x02\x02JK\f\x03\x02\x02KL\x07\x0F\x02\x02LN\x05\b\x05\x02M" + + "J\x03\x02\x02\x02NQ\x03\x02\x02\x02OM\x03\x02\x02\x02OP\x03\x02\x02\x02" + + "P\x05\x03\x02\x02\x02QO\x03\x02\x02\x02RV\x05@!\x02SV\x05\x1A\x0E\x02" + + "TV\x05\x14\v\x02UR\x03\x02\x02\x02US\x03\x02\x02\x02UT\x03\x02\x02\x02" + + "V\x07\x03\x02\x02\x02W^\x05\x1C\x0F\x02X^\x05.\x18\x02Y^\x054\x1B\x02" + + "Z^\x050\x19\x02[^\x05\x1E\x10\x02\\^\x05\n\x06\x02]W\x03\x02\x02\x02]" + + "X\x03\x02\x02\x02]Y\x03\x02\x02\x02]Z\x03\x02\x02\x02][\x03\x02\x02\x02" + + "]\\\x03\x02\x02\x02^\t\x03\x02\x02\x02_`\x07\b\x02\x02`a\x05\f\x07\x02" + + "a\v\x03\x02\x02\x02bc\b\x07\x01\x02cd\x07 \x02\x02dg\x05\f\x07\x06eg\x05" + + "\x0E\b\x02fb\x03\x02\x02\x02fe\x03\x02\x02\x02gp\x03\x02\x02\x02hi\f\x04" + + "\x02\x02ij\x07\x14\x02\x02jo\x05\f\x07\x05kl\f\x03\x02\x02lm\x07#\x02" + + "\x02mo\x05\f\x07\x04nh\x03\x02\x02\x02nk\x03\x02\x02\x02or\x03\x02\x02" + + "\x02pn\x03\x02\x02\x02pq\x03\x02\x02\x02q\r\x03\x02\x02\x02rp\x03\x02" + + "\x02\x02st\x05*\x16\x02t}\x07\x1D\x02\x02uz\x05\"\x12\x02vw\x07\x17\x02" + + "\x02wy\x05\"\x12\x02xv\x03\x02\x02\x02y|\x03\x02\x02\x02zx\x03\x02\x02" + + "\x02z{\x03\x02\x02\x02{~\x03\x02\x02\x02|z\x03\x02\x02\x02}u\x03\x02\x02" + + "\x02}~\x03\x02\x02\x02~\x7F\x03\x02\x02\x02\x7F\x80\x07$\x02\x02\x80\x87" + + "\x03\x02\x02\x02\x81\x87\x05\x10\t\x02\x82\x83\x05\x10\t\x02\x83\x84\x05" + + "> \x02\x84\x85\x05\x10\t\x02\x85\x87\x03\x02\x02\x02\x86s\x03\x02\x02" + + "\x02\x86\x81\x03\x02\x02\x02\x86\x82\x03\x02\x02\x02\x87\x0F\x03\x02\x02" + + "\x02\x88\x89\b\t\x01\x02\x89\x8D\x05\x12\n\x02\x8A\x8B\t\x02\x02\x02\x8B" + + "\x8D\x05\x10\t\x05\x8C\x88\x03\x02\x02\x02\x8C\x8A\x03\x02\x02\x02\x8D" + + "\x96\x03\x02\x02\x02\x8E\x8F\f\x04\x02\x02\x8F\x90\t\x03\x02\x02\x90\x95" + + "\x05\x10\t\x05\x91\x92\f\x03\x02\x02\x92\x93\t\x02\x02\x02\x93\x95\x05" + + "\x10\t\x04\x94\x8E\x03\x02\x02\x02\x94\x91\x03\x02\x02\x02\x95\x98\x03" + + "\x02\x02\x02\x96\x94\x03\x02\x02\x02\x96\x97\x03\x02\x02\x02\x97\x11\x03" + + "\x02\x02\x02\x98\x96\x03\x02\x02\x02\x99\xAE\x05,\x17\x02\x9A\xAE\x05" + + "$\x13\x02\x9B\x9C\x07\x1D\x02\x02\x9C\x9D\x05\f\x07\x02\x9D\x9E\x07$\x02" + + "\x02\x9E\xAE\x03\x02\x02\x02\x9F\xA0\x05(\x15\x02\xA0\xA9\x07\x1D\x02" + + "\x02\xA1\xA6\x05\f\x07\x02\xA2\xA3\x07\x17\x02\x02\xA3\xA5\x05\f\x07\x02" + + "\xA4\xA2\x03\x02\x02\x02\xA5\xA8\x03\x02\x02\x02\xA6\xA4\x03\x02\x02\x02" + + "\xA6\xA7\x03\x02\x02\x02\xA7\xAA\x03\x02\x02\x02\xA8\xA6\x03\x02\x02\x02" + + "\xA9\xA1\x03\x02\x02\x02\xA9\xAA\x03\x02\x02\x02\xAA\xAB\x03\x02\x02\x02" + + "\xAB\xAC\x07$\x02\x02\xAC\xAE\x03\x02\x02\x02\xAD\x99\x03\x02\x02\x02" + + "\xAD\x9A\x03\x02\x02\x02\xAD\x9B\x03\x02\x02\x02\xAD\x9F\x03\x02\x02\x02" + + "\xAE\x13\x03\x02\x02\x02\xAF\xB0\x07\x06\x02\x02\xB0\xB1\x05\x16\f\x02" + + "\xB1\x15\x03\x02\x02\x02\xB2\xB7\x05\x18\r\x02\xB3\xB4\x07\x17\x02\x02" + + "\xB4\xB6\x05\x18\r\x02\xB5\xB3\x03\x02\x02\x02\xB6\xB9\x03\x02\x02\x02" + + "\xB7\xB5\x03\x02\x02\x02\xB7\xB8\x03\x02\x02\x02\xB8\x17\x03\x02\x02\x02" + + "\xB9\xB7\x03\x02\x02\x02\xBA\xBB\x05$\x13\x02\xBB\xBC\x07\x16\x02\x02" + + "\xBC\xBD\x05\x0E\b\x02\xBD\xC4\x03\x02\x02\x02\xBE\xC4\x05\f\x07\x02\xBF" + + "\xC0\x05$\x13\x02\xC0\xC1\x07\x16\x02\x02\xC1\xC2\x05\f\x07\x02\xC2\xC4" + + "\x03\x02\x02\x02\xC3\xBA\x03\x02\x02\x02\xC3\xBE\x03\x02\x02\x02\xC3\xBF" + + "\x03\x02\x02\x02\xC4\x19\x03\x02\x02\x02\xC5\xC6\x07\x05\x02\x02\xC6\xCB" + + "\x05 \x11\x02\xC7\xC8\x07\x17\x02\x02\xC8\xCA\x05 \x11\x02\xC9\xC7\x03" + + "\x02\x02\x02\xCA\xCD\x03\x02\x02\x02\xCB\xC9\x03\x02\x02\x02\xCB\xCC\x03" + + "\x02\x02\x02\xCC\x1B\x03\x02\x02\x02\xCD\xCB\x03\x02\x02\x02\xCE\xCF\x07" + + "\x03\x02\x02\xCF\xD0\x05\x16\f\x02\xD0\x1D\x03\x02\x02\x02\xD1\xD2\x07" + + "\x07\x02\x02\xD2\xD5\x05\x16\f\x02\xD3\xD4\x07\x13\x02\x02\xD4\xD6\x05" + + "&\x14\x02\xD5\xD3\x03\x02\x02\x02\xD5\xD6\x03\x02\x02\x02\xD6\x1F\x03" + + "\x02\x02\x02\xD7\xD8\t\x04\x02\x02\xD8!\x03\x02\x02\x02\xD9\xDC\x05$\x13" + + "\x02\xDA\xDC\x05<\x1F\x02\xDB\xD9\x03\x02\x02\x02\xDB\xDA\x03\x02\x02" + + "\x02\xDC#\x03\x02\x02\x02\xDD\xE2\x05(\x15\x02\xDE\xDF\x07\x19\x02\x02" + + "\xDF\xE1\x05(\x15\x02\xE0\xDE\x03\x02\x02\x02\xE1\xE4\x03\x02\x02\x02" + + "\xE2\xE0\x03\x02\x02\x02\xE2\xE3\x03\x02\x02\x02\xE3%\x03\x02\x02\x02" + + "\xE4\xE2\x03\x02\x02\x02\xE5\xEA\x05$\x13\x02\xE6\xE7\x07\x17\x02\x02" + + "\xE7\xE9\x05$\x13\x02\xE8\xE6\x03\x02\x02\x02\xE9\xEC\x03\x02\x02\x02" + + "\xEA\xE8\x03\x02\x02\x02\xEA\xEB\x03\x02\x02\x02\xEB\'\x03\x02\x02\x02" + + "\xEC\xEA\x03\x02\x02\x02\xED\xEE\t\x05\x02\x02\xEE)\x03\x02\x02\x02\xEF" + + "\xF0\t\x06\x02\x02\xF0+\x03\x02\x02\x02\xF1\xF6\x07!\x02\x02\xF2\xF6\x05" + + ":\x1E\x02\xF3\xF6\x058\x1D\x02\xF4\xF6\x05<\x1F\x02\xF5\xF1\x03\x02\x02" + + "\x02\xF5\xF2\x03\x02\x02\x02\xF5\xF3\x03\x02\x02\x02\xF5\xF4\x03\x02\x02" + + "\x02\xF6-\x03\x02\x02\x02\xF7\xF8\x07\n\x02\x02\xF8\xF9\x07\x11\x02\x02" + + "\xF9/\x03\x02\x02\x02\xFA\xFB\x07\t\x02\x02\xFB\u0100\x052\x1A\x02\xFC" + + "\xFD\x07\x17\x02\x02\xFD\xFF\x052\x1A\x02\xFE\xFC\x03\x02\x02\x02\xFF" + + "\u0102\x03\x02\x02\x02\u0100\xFE\x03\x02\x02\x02\u0100\u0101\x03\x02\x02" + + "\x02\u01011\x03\x02\x02\x02\u0102\u0100\x03\x02\x02\x02\u0103\u0105\x05" + + "\f\x07\x02\u0104\u0106\t\x07\x02\x02\u0105\u0104\x03\x02\x02\x02\u0105" + + "\u0106\x03\x02\x02\x02\u0106\u0109\x03\x02\x02\x02\u0107\u0108\x07\"\x02" + + "\x02\u0108\u010A\t\b\x02\x02\u0109\u0107\x03\x02\x02\x02\u0109\u010A\x03" + + "\x02\x02\x02\u010A3\x03\x02\x02\x02\u010B\u010C\x07\v\x02\x02\u010C\u0111" + + "\x056\x1C\x02\u010D\u010E\x07\x17\x02\x02\u010E\u0110\x056\x1C\x02\u010F" + + "\u010D\x03\x02\x02\x02\u0110\u0113\x03\x02\x02\x02\u0111\u010F\x03\x02" + + "\x02\x02\u0111\u0112\x03\x02\x02\x02\u01125\x03\x02\x02\x02\u0113\u0111" + + "\x03\x02\x02\x02\u0114\u011A\x05 \x11\x02\u0115\u0116\x05 \x11\x02\u0116" + + "\u0117\x07\x16\x02\x02\u0117\u0118\x05 \x11\x02\u0118\u011A\x03\x02\x02" + + "\x02\u0119\u0114\x03\x02\x02\x02\u0119\u0115\x03\x02\x02\x02\u011A7\x03" + + "\x02\x02\x02\u011B\u011C\t\t\x02\x02\u011C9\x03\x02\x02\x02\u011D\u0120" + + "\x07\x12\x02\x02\u011E\u0120\x07\x11\x02\x02\u011F\u011D\x03\x02\x02\x02" + + "\u011F\u011E\x03\x02\x02\x02\u0120;\x03\x02\x02\x02\u0121\u0122\x07\x10" + + "\x02\x02\u0122=\x03\x02\x02\x02\u0123\u0124\t\n\x02\x02\u0124?\x03\x02" + + "\x02\x02\u0125\u0126\x07\x04\x02\x02\u0126\u0127\x05B\"\x02\u0127A\x03" + + "\x02\x02\x02\u0128\u0129\x07\x1E\x02\x02\u0129\u012A\x05\x04\x03\x02\u012A" + + "\u012B\x07\x1F\x02\x02\u012BC\x03\x02\x02\x02\x1FOU]fnpz}\x86\x8C\x94" + + "\x96\xA6\xA9\xAD\xB7\xC3\xCB\xD5\xDB\xE2\xEA\xF5\u0100\u0105\u0109\u0111" + + "\u0119\u011F"; + public static __ATN: ATN; + public static get _ATN(): ATN { + if (!esql_parser.__ATN) { + esql_parser.__ATN = new ATNDeserializer().deserialize(Utils.toCharArray(esql_parser._serializedATN)); + } + + return esql_parser.__ATN; + } + +} + +export class SingleStatementContext extends ParserRuleContext { + public query(): QueryContext { + return this.getRuleContext(0, QueryContext); + } + public EOF(): TerminalNode { return this.getToken(esql_parser.EOF, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_singleStatement; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterSingleStatement) { + listener.enterSingleStatement(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitSingleStatement) { + listener.exitSingleStatement(this); + } + } +} + + +export class QueryContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_query; } + public copyFrom(ctx: QueryContext): void { + super.copyFrom(ctx); + } +} +export class SingleCommandQueryContext extends QueryContext { + public sourceCommand(): SourceCommandContext { + return this.getRuleContext(0, SourceCommandContext); + } + constructor(ctx: QueryContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterSingleCommandQuery) { + listener.enterSingleCommandQuery(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitSingleCommandQuery) { + listener.exitSingleCommandQuery(this); + } + } +} +export class CompositeQueryContext extends QueryContext { + public query(): QueryContext { + return this.getRuleContext(0, QueryContext); + } + public PIPE(): TerminalNode { return this.getToken(esql_parser.PIPE, 0); } + public processingCommand(): ProcessingCommandContext { + return this.getRuleContext(0, ProcessingCommandContext); + } + constructor(ctx: QueryContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterCompositeQuery) { + listener.enterCompositeQuery(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitCompositeQuery) { + listener.exitCompositeQuery(this); + } + } +} + + +export class SourceCommandContext extends ParserRuleContext { + public explainCommand(): ExplainCommandContext | undefined { + return this.tryGetRuleContext(0, ExplainCommandContext); + } + public fromCommand(): FromCommandContext | undefined { + return this.tryGetRuleContext(0, FromCommandContext); + } + public rowCommand(): RowCommandContext | undefined { + return this.tryGetRuleContext(0, RowCommandContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_sourceCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterSourceCommand) { + listener.enterSourceCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitSourceCommand) { + listener.exitSourceCommand(this); + } + } +} + + +export class ProcessingCommandContext extends ParserRuleContext { + public evalCommand(): EvalCommandContext | undefined { + return this.tryGetRuleContext(0, EvalCommandContext); + } + public limitCommand(): LimitCommandContext | undefined { + return this.tryGetRuleContext(0, LimitCommandContext); + } + public projectCommand(): ProjectCommandContext | undefined { + return this.tryGetRuleContext(0, ProjectCommandContext); + } + public sortCommand(): SortCommandContext | undefined { + return this.tryGetRuleContext(0, SortCommandContext); + } + public statsCommand(): StatsCommandContext | undefined { + return this.tryGetRuleContext(0, StatsCommandContext); + } + public whereCommand(): WhereCommandContext | undefined { + return this.tryGetRuleContext(0, WhereCommandContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_processingCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterProcessingCommand) { + listener.enterProcessingCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitProcessingCommand) { + listener.exitProcessingCommand(this); + } + } +} + + +export class WhereCommandContext extends ParserRuleContext { + public WHERE(): TerminalNode { return this.getToken(esql_parser.WHERE, 0); } + public booleanExpression(): BooleanExpressionContext { + return this.getRuleContext(0, BooleanExpressionContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_whereCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterWhereCommand) { + listener.enterWhereCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitWhereCommand) { + listener.exitWhereCommand(this); + } + } +} + + +export class BooleanExpressionContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_booleanExpression; } + public copyFrom(ctx: BooleanExpressionContext): void { + super.copyFrom(ctx); + } +} +export class LogicalNotContext extends BooleanExpressionContext { + public NOT(): TerminalNode { return this.getToken(esql_parser.NOT, 0); } + public booleanExpression(): BooleanExpressionContext { + return this.getRuleContext(0, BooleanExpressionContext); + } + constructor(ctx: BooleanExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterLogicalNot) { + listener.enterLogicalNot(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitLogicalNot) { + listener.exitLogicalNot(this); + } + } +} +export class BooleanDefaultContext extends BooleanExpressionContext { + public valueExpression(): ValueExpressionContext { + return this.getRuleContext(0, ValueExpressionContext); + } + constructor(ctx: BooleanExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterBooleanDefault) { + listener.enterBooleanDefault(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitBooleanDefault) { + listener.exitBooleanDefault(this); + } + } +} +export class LogicalBinaryContext extends BooleanExpressionContext { + public _left: BooleanExpressionContext; + public _operator: Token; + public _right: BooleanExpressionContext; + public booleanExpression(): BooleanExpressionContext[]; + public booleanExpression(i: number): BooleanExpressionContext; + public booleanExpression(i?: number): BooleanExpressionContext | BooleanExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(BooleanExpressionContext); + } else { + return this.getRuleContext(i, BooleanExpressionContext); + } + } + public AND(): TerminalNode | undefined { return this.tryGetToken(esql_parser.AND, 0); } + public OR(): TerminalNode | undefined { return this.tryGetToken(esql_parser.OR, 0); } + constructor(ctx: BooleanExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterLogicalBinary) { + listener.enterLogicalBinary(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitLogicalBinary) { + listener.exitLogicalBinary(this); + } + } +} + + +export class ValueExpressionContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_valueExpression; } + public copyFrom(ctx: ValueExpressionContext): void { + super.copyFrom(ctx); + } +} +export class ValueFunctionExpressionContext extends ValueExpressionContext { + public functionIdentifier(): FunctionIdentifierContext { + return this.getRuleContext(0, FunctionIdentifierContext); + } + public LP(): TerminalNode { return this.getToken(esql_parser.LP, 0); } + public RP(): TerminalNode { return this.getToken(esql_parser.RP, 0); } + public functionExpressionArgument(): FunctionExpressionArgumentContext[]; + public functionExpressionArgument(i: number): FunctionExpressionArgumentContext; + public functionExpressionArgument(i?: number): FunctionExpressionArgumentContext | FunctionExpressionArgumentContext[] { + if (i === undefined) { + return this.getRuleContexts(FunctionExpressionArgumentContext); + } else { + return this.getRuleContext(i, FunctionExpressionArgumentContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + constructor(ctx: ValueExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterValueFunctionExpression) { + listener.enterValueFunctionExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitValueFunctionExpression) { + listener.exitValueFunctionExpression(this); + } + } +} +export class ValueExpressionDefaultContext extends ValueExpressionContext { + public operatorExpression(): OperatorExpressionContext { + return this.getRuleContext(0, OperatorExpressionContext); + } + constructor(ctx: ValueExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterValueExpressionDefault) { + listener.enterValueExpressionDefault(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitValueExpressionDefault) { + listener.exitValueExpressionDefault(this); + } + } +} +export class ComparisonContext extends ValueExpressionContext { + public _left: OperatorExpressionContext; + public _right: OperatorExpressionContext; + public comparisonOperator(): ComparisonOperatorContext { + return this.getRuleContext(0, ComparisonOperatorContext); + } + public operatorExpression(): OperatorExpressionContext[]; + public operatorExpression(i: number): OperatorExpressionContext; + public operatorExpression(i?: number): OperatorExpressionContext | OperatorExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(OperatorExpressionContext); + } else { + return this.getRuleContext(i, OperatorExpressionContext); + } + } + constructor(ctx: ValueExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterComparison) { + listener.enterComparison(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitComparison) { + listener.exitComparison(this); + } + } +} + + +export class OperatorExpressionContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_operatorExpression; } + public copyFrom(ctx: OperatorExpressionContext): void { + super.copyFrom(ctx); + } +} +export class OperatorExpressionDefaultContext extends OperatorExpressionContext { + public primaryExpression(): PrimaryExpressionContext { + return this.getRuleContext(0, PrimaryExpressionContext); + } + constructor(ctx: OperatorExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterOperatorExpressionDefault) { + listener.enterOperatorExpressionDefault(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitOperatorExpressionDefault) { + listener.exitOperatorExpressionDefault(this); + } + } +} +export class ArithmeticUnaryContext extends OperatorExpressionContext { + public _operator: Token; + public operatorExpression(): OperatorExpressionContext { + return this.getRuleContext(0, OperatorExpressionContext); + } + public MINUS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.MINUS, 0); } + public PLUS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.PLUS, 0); } + constructor(ctx: OperatorExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterArithmeticUnary) { + listener.enterArithmeticUnary(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitArithmeticUnary) { + listener.exitArithmeticUnary(this); + } + } +} +export class ArithmeticBinaryContext extends OperatorExpressionContext { + public _left: OperatorExpressionContext; + public _operator: Token; + public _right: OperatorExpressionContext; + public operatorExpression(): OperatorExpressionContext[]; + public operatorExpression(i: number): OperatorExpressionContext; + public operatorExpression(i?: number): OperatorExpressionContext | OperatorExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(OperatorExpressionContext); + } else { + return this.getRuleContext(i, OperatorExpressionContext); + } + } + public ASTERISK(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASTERISK, 0); } + public SLASH(): TerminalNode | undefined { return this.tryGetToken(esql_parser.SLASH, 0); } + public PERCENT(): TerminalNode | undefined { return this.tryGetToken(esql_parser.PERCENT, 0); } + public PLUS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.PLUS, 0); } + public MINUS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.MINUS, 0); } + constructor(ctx: OperatorExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterArithmeticBinary) { + listener.enterArithmeticBinary(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitArithmeticBinary) { + listener.exitArithmeticBinary(this); + } + } +} + + +export class PrimaryExpressionContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_primaryExpression; } + public copyFrom(ctx: PrimaryExpressionContext): void { + super.copyFrom(ctx); + } +} +export class ConstantDefaultContext extends PrimaryExpressionContext { + public constant(): ConstantContext { + return this.getRuleContext(0, ConstantContext); + } + constructor(ctx: PrimaryExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterConstantDefault) { + listener.enterConstantDefault(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitConstantDefault) { + listener.exitConstantDefault(this); + } + } +} +export class DereferenceContext extends PrimaryExpressionContext { + public qualifiedName(): QualifiedNameContext { + return this.getRuleContext(0, QualifiedNameContext); + } + constructor(ctx: PrimaryExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterDereference) { + listener.enterDereference(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitDereference) { + listener.exitDereference(this); + } + } +} +export class ParenthesizedExpressionContext extends PrimaryExpressionContext { + public LP(): TerminalNode { return this.getToken(esql_parser.LP, 0); } + public booleanExpression(): BooleanExpressionContext { + return this.getRuleContext(0, BooleanExpressionContext); + } + public RP(): TerminalNode { return this.getToken(esql_parser.RP, 0); } + constructor(ctx: PrimaryExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterParenthesizedExpression) { + listener.enterParenthesizedExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitParenthesizedExpression) { + listener.exitParenthesizedExpression(this); + } + } +} +export class FunctionExpressionContext extends PrimaryExpressionContext { + public identifier(): IdentifierContext { + return this.getRuleContext(0, IdentifierContext); + } + public LP(): TerminalNode { return this.getToken(esql_parser.LP, 0); } + public RP(): TerminalNode { return this.getToken(esql_parser.RP, 0); } + public booleanExpression(): BooleanExpressionContext[]; + public booleanExpression(i: number): BooleanExpressionContext; + public booleanExpression(i?: number): BooleanExpressionContext | BooleanExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(BooleanExpressionContext); + } else { + return this.getRuleContext(i, BooleanExpressionContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + constructor(ctx: PrimaryExpressionContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterFunctionExpression) { + listener.enterFunctionExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitFunctionExpression) { + listener.exitFunctionExpression(this); + } + } +} + + +export class RowCommandContext extends ParserRuleContext { + public ROW(): TerminalNode { return this.getToken(esql_parser.ROW, 0); } + public fields(): FieldsContext { + return this.getRuleContext(0, FieldsContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_rowCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterRowCommand) { + listener.enterRowCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitRowCommand) { + listener.exitRowCommand(this); + } + } +} + + +export class FieldsContext extends ParserRuleContext { + public field(): FieldContext[]; + public field(i: number): FieldContext; + public field(i?: number): FieldContext | FieldContext[] { + if (i === undefined) { + return this.getRuleContexts(FieldContext); + } else { + return this.getRuleContext(i, FieldContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_fields; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterFields) { + listener.enterFields(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitFields) { + listener.exitFields(this); + } + } +} + + +export class FieldContext extends ParserRuleContext { + public qualifiedName(): QualifiedNameContext | undefined { + return this.tryGetRuleContext(0, QualifiedNameContext); + } + public ASSIGN(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASSIGN, 0); } + public valueExpression(): ValueExpressionContext | undefined { + return this.tryGetRuleContext(0, ValueExpressionContext); + } + public booleanExpression(): BooleanExpressionContext | undefined { + return this.tryGetRuleContext(0, BooleanExpressionContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_field; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterField) { + listener.enterField(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitField) { + listener.exitField(this); + } + } +} + + +export class FromCommandContext extends ParserRuleContext { + public FROM(): TerminalNode { return this.getToken(esql_parser.FROM, 0); } + public sourceIdentifier(): SourceIdentifierContext[]; + public sourceIdentifier(i: number): SourceIdentifierContext; + public sourceIdentifier(i?: number): SourceIdentifierContext | SourceIdentifierContext[] { + if (i === undefined) { + return this.getRuleContexts(SourceIdentifierContext); + } else { + return this.getRuleContext(i, SourceIdentifierContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_fromCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterFromCommand) { + listener.enterFromCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitFromCommand) { + listener.exitFromCommand(this); + } + } +} + + +export class EvalCommandContext extends ParserRuleContext { + public EVAL(): TerminalNode { return this.getToken(esql_parser.EVAL, 0); } + public fields(): FieldsContext { + return this.getRuleContext(0, FieldsContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_evalCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterEvalCommand) { + listener.enterEvalCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitEvalCommand) { + listener.exitEvalCommand(this); + } + } +} + + +export class StatsCommandContext extends ParserRuleContext { + public STATS(): TerminalNode { return this.getToken(esql_parser.STATS, 0); } + public fields(): FieldsContext { + return this.getRuleContext(0, FieldsContext); + } + public BY(): TerminalNode | undefined { return this.tryGetToken(esql_parser.BY, 0); } + public qualifiedNames(): QualifiedNamesContext | undefined { + return this.tryGetRuleContext(0, QualifiedNamesContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_statsCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterStatsCommand) { + listener.enterStatsCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitStatsCommand) { + listener.exitStatsCommand(this); + } + } +} + + +export class SourceIdentifierContext extends ParserRuleContext { + public SRC_UNQUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.SRC_UNQUOTED_IDENTIFIER, 0); } + public SRC_QUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.SRC_QUOTED_IDENTIFIER, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_sourceIdentifier; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterSourceIdentifier) { + listener.enterSourceIdentifier(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitSourceIdentifier) { + listener.exitSourceIdentifier(this); + } + } +} + + +export class FunctionExpressionArgumentContext extends ParserRuleContext { + public qualifiedName(): QualifiedNameContext | undefined { + return this.tryGetRuleContext(0, QualifiedNameContext); + } + public string(): StringContext | undefined { + return this.tryGetRuleContext(0, StringContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_functionExpressionArgument; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterFunctionExpressionArgument) { + listener.enterFunctionExpressionArgument(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitFunctionExpressionArgument) { + listener.exitFunctionExpressionArgument(this); + } + } +} + + +export class QualifiedNameContext extends ParserRuleContext { + public identifier(): IdentifierContext[]; + public identifier(i: number): IdentifierContext; + public identifier(i?: number): IdentifierContext | IdentifierContext[] { + if (i === undefined) { + return this.getRuleContexts(IdentifierContext); + } else { + return this.getRuleContext(i, IdentifierContext); + } + } + public DOT(): TerminalNode[]; + public DOT(i: number): TerminalNode; + public DOT(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.DOT); + } else { + return this.getToken(esql_parser.DOT, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_qualifiedName; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterQualifiedName) { + listener.enterQualifiedName(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitQualifiedName) { + listener.exitQualifiedName(this); + } + } +} + + +export class QualifiedNamesContext extends ParserRuleContext { + public qualifiedName(): QualifiedNameContext[]; + public qualifiedName(i: number): QualifiedNameContext; + public qualifiedName(i?: number): QualifiedNameContext | QualifiedNameContext[] { + if (i === undefined) { + return this.getRuleContexts(QualifiedNameContext); + } else { + return this.getRuleContext(i, QualifiedNameContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_qualifiedNames; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterQualifiedNames) { + listener.enterQualifiedNames(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitQualifiedNames) { + listener.exitQualifiedNames(this); + } + } +} + + +export class IdentifierContext extends ParserRuleContext { + public UNQUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.UNQUOTED_IDENTIFIER, 0); } + public QUOTED_IDENTIFIER(): TerminalNode | undefined { return this.tryGetToken(esql_parser.QUOTED_IDENTIFIER, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_identifier; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterIdentifier) { + listener.enterIdentifier(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitIdentifier) { + listener.exitIdentifier(this); + } + } +} + + +export class FunctionIdentifierContext extends ParserRuleContext { + public ROUND_FUNCTION_MATH(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ROUND_FUNCTION_MATH, 0); } + public AVG_FUNCTION_MATH(): TerminalNode | undefined { return this.tryGetToken(esql_parser.AVG_FUNCTION_MATH, 0); } + public SUM_FUNCTION_MATH(): TerminalNode | undefined { return this.tryGetToken(esql_parser.SUM_FUNCTION_MATH, 0); } + public MIN_FUNCTION_MATH(): TerminalNode | undefined { return this.tryGetToken(esql_parser.MIN_FUNCTION_MATH, 0); } + public MAX_FUNCTION_MATH(): TerminalNode | undefined { return this.tryGetToken(esql_parser.MAX_FUNCTION_MATH, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_functionIdentifier; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterFunctionIdentifier) { + listener.enterFunctionIdentifier(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitFunctionIdentifier) { + listener.exitFunctionIdentifier(this); + } + } +} + + +export class ConstantContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_constant; } + public copyFrom(ctx: ConstantContext): void { + super.copyFrom(ctx); + } +} +export class NullLiteralContext extends ConstantContext { + public NULL(): TerminalNode { return this.getToken(esql_parser.NULL, 0); } + constructor(ctx: ConstantContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterNullLiteral) { + listener.enterNullLiteral(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitNullLiteral) { + listener.exitNullLiteral(this); + } + } +} +export class NumericLiteralContext extends ConstantContext { + public number(): NumberContext { + return this.getRuleContext(0, NumberContext); + } + constructor(ctx: ConstantContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterNumericLiteral) { + listener.enterNumericLiteral(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitNumericLiteral) { + listener.exitNumericLiteral(this); + } + } +} +export class BooleanLiteralContext extends ConstantContext { + public booleanValue(): BooleanValueContext { + return this.getRuleContext(0, BooleanValueContext); + } + constructor(ctx: ConstantContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterBooleanLiteral) { + listener.enterBooleanLiteral(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitBooleanLiteral) { + listener.exitBooleanLiteral(this); + } + } +} +export class StringLiteralContext extends ConstantContext { + public string(): StringContext { + return this.getRuleContext(0, StringContext); + } + constructor(ctx: ConstantContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterStringLiteral) { + listener.enterStringLiteral(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitStringLiteral) { + listener.exitStringLiteral(this); + } + } +} + + +export class LimitCommandContext extends ParserRuleContext { + public LIMIT(): TerminalNode { return this.getToken(esql_parser.LIMIT, 0); } + public INTEGER_LITERAL(): TerminalNode { return this.getToken(esql_parser.INTEGER_LITERAL, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_limitCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterLimitCommand) { + listener.enterLimitCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitLimitCommand) { + listener.exitLimitCommand(this); + } + } +} + + +export class SortCommandContext extends ParserRuleContext { + public SORT(): TerminalNode { return this.getToken(esql_parser.SORT, 0); } + public orderExpression(): OrderExpressionContext[]; + public orderExpression(i: number): OrderExpressionContext; + public orderExpression(i?: number): OrderExpressionContext | OrderExpressionContext[] { + if (i === undefined) { + return this.getRuleContexts(OrderExpressionContext); + } else { + return this.getRuleContext(i, OrderExpressionContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_sortCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterSortCommand) { + listener.enterSortCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitSortCommand) { + listener.exitSortCommand(this); + } + } +} + + +export class OrderExpressionContext extends ParserRuleContext { + public _ordering: Token; + public _nullOrdering: Token; + public booleanExpression(): BooleanExpressionContext { + return this.getRuleContext(0, BooleanExpressionContext); + } + public NULLS(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NULLS, 0); } + public ASC(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASC, 0); } + public DESC(): TerminalNode | undefined { return this.tryGetToken(esql_parser.DESC, 0); } + public FIRST(): TerminalNode | undefined { return this.tryGetToken(esql_parser.FIRST, 0); } + public LAST(): TerminalNode | undefined { return this.tryGetToken(esql_parser.LAST, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_orderExpression; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterOrderExpression) { + listener.enterOrderExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitOrderExpression) { + listener.exitOrderExpression(this); + } + } +} + + +export class ProjectCommandContext extends ParserRuleContext { + public PROJECT(): TerminalNode { return this.getToken(esql_parser.PROJECT, 0); } + public projectClause(): ProjectClauseContext[]; + public projectClause(i: number): ProjectClauseContext; + public projectClause(i?: number): ProjectClauseContext | ProjectClauseContext[] { + if (i === undefined) { + return this.getRuleContexts(ProjectClauseContext); + } else { + return this.getRuleContext(i, ProjectClauseContext); + } + } + public COMMA(): TerminalNode[]; + public COMMA(i: number): TerminalNode; + public COMMA(i?: number): TerminalNode | TerminalNode[] { + if (i === undefined) { + return this.getTokens(esql_parser.COMMA); + } else { + return this.getToken(esql_parser.COMMA, i); + } + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_projectCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterProjectCommand) { + listener.enterProjectCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitProjectCommand) { + listener.exitProjectCommand(this); + } + } +} + + +export class ProjectClauseContext extends ParserRuleContext { + public _newName: SourceIdentifierContext; + public _oldName: SourceIdentifierContext; + public sourceIdentifier(): SourceIdentifierContext[]; + public sourceIdentifier(i: number): SourceIdentifierContext; + public sourceIdentifier(i?: number): SourceIdentifierContext | SourceIdentifierContext[] { + if (i === undefined) { + return this.getRuleContexts(SourceIdentifierContext); + } else { + return this.getRuleContext(i, SourceIdentifierContext); + } + } + public ASSIGN(): TerminalNode | undefined { return this.tryGetToken(esql_parser.ASSIGN, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_projectClause; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterProjectClause) { + listener.enterProjectClause(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitProjectClause) { + listener.exitProjectClause(this); + } + } +} + + +export class BooleanValueContext extends ParserRuleContext { + public TRUE(): TerminalNode | undefined { return this.tryGetToken(esql_parser.TRUE, 0); } + public FALSE(): TerminalNode | undefined { return this.tryGetToken(esql_parser.FALSE, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_booleanValue; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterBooleanValue) { + listener.enterBooleanValue(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitBooleanValue) { + listener.exitBooleanValue(this); + } + } +} + + +export class NumberContext extends ParserRuleContext { + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_number; } + public copyFrom(ctx: NumberContext): void { + super.copyFrom(ctx); + } +} +export class DecimalLiteralContext extends NumberContext { + public DECIMAL_LITERAL(): TerminalNode { return this.getToken(esql_parser.DECIMAL_LITERAL, 0); } + constructor(ctx: NumberContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterDecimalLiteral) { + listener.enterDecimalLiteral(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitDecimalLiteral) { + listener.exitDecimalLiteral(this); + } + } +} +export class IntegerLiteralContext extends NumberContext { + public INTEGER_LITERAL(): TerminalNode { return this.getToken(esql_parser.INTEGER_LITERAL, 0); } + constructor(ctx: NumberContext) { + super(ctx.parent, ctx.invokingState); + this.copyFrom(ctx); + } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterIntegerLiteral) { + listener.enterIntegerLiteral(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitIntegerLiteral) { + listener.exitIntegerLiteral(this); + } + } +} + + +export class StringContext extends ParserRuleContext { + public STRING(): TerminalNode { return this.getToken(esql_parser.STRING, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_string; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterString) { + listener.enterString(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitString) { + listener.exitString(this); + } + } +} + + +export class ComparisonOperatorContext extends ParserRuleContext { + public EQ(): TerminalNode | undefined { return this.tryGetToken(esql_parser.EQ, 0); } + public NEQ(): TerminalNode | undefined { return this.tryGetToken(esql_parser.NEQ, 0); } + public LT(): TerminalNode | undefined { return this.tryGetToken(esql_parser.LT, 0); } + public LTE(): TerminalNode | undefined { return this.tryGetToken(esql_parser.LTE, 0); } + public GT(): TerminalNode | undefined { return this.tryGetToken(esql_parser.GT, 0); } + public GTE(): TerminalNode | undefined { return this.tryGetToken(esql_parser.GTE, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_comparisonOperator; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterComparisonOperator) { + listener.enterComparisonOperator(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitComparisonOperator) { + listener.exitComparisonOperator(this); + } + } +} + + +export class ExplainCommandContext extends ParserRuleContext { + public EXPLAIN(): TerminalNode { return this.getToken(esql_parser.EXPLAIN, 0); } + public subqueryExpression(): SubqueryExpressionContext { + return this.getRuleContext(0, SubqueryExpressionContext); + } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_explainCommand; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterExplainCommand) { + listener.enterExplainCommand(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitExplainCommand) { + listener.exitExplainCommand(this); + } + } +} + + +export class SubqueryExpressionContext extends ParserRuleContext { + public OPENING_BRACKET(): TerminalNode { return this.getToken(esql_parser.OPENING_BRACKET, 0); } + public query(): QueryContext { + return this.getRuleContext(0, QueryContext); + } + public CLOSING_BRACKET(): TerminalNode { return this.getToken(esql_parser.CLOSING_BRACKET, 0); } + constructor(parent: ParserRuleContext | undefined, invokingState: number) { + super(parent, invokingState); + } + // @Override + public get ruleIndex(): number { return esql_parser.RULE_subqueryExpression; } + // @Override + public enterRule(listener: esql_parserListener): void { + if (listener.enterSubqueryExpression) { + listener.enterSubqueryExpression(this); + } + } + // @Override + public exitRule(listener: esql_parserListener): void { + if (listener.exitSubqueryExpression) { + listener.exitSubqueryExpression(this); + } + } +} + + diff --git a/packages/kbn-monaco/src/esql/antlr/esql_parser_listener.ts b/packages/kbn-monaco/src/esql/antlr/esql_parser_listener.ts new file mode 100644 index 0000000000000..d3a3c68a13941 --- /dev/null +++ b/packages/kbn-monaco/src/esql/antlr/esql_parser_listener.ts @@ -0,0 +1,704 @@ +// @ts-nocheck +// Generated from src/esql/antlr/esql_parser.g4 by ANTLR 4.7.3-SNAPSHOT + + +import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener"; + +import { ValueFunctionExpressionContext } from "./esql_parser"; +import { ValueExpressionDefaultContext } from "./esql_parser"; +import { ComparisonContext } from "./esql_parser"; +import { NullLiteralContext } from "./esql_parser"; +import { NumericLiteralContext } from "./esql_parser"; +import { BooleanLiteralContext } from "./esql_parser"; +import { StringLiteralContext } from "./esql_parser"; +import { DecimalLiteralContext } from "./esql_parser"; +import { IntegerLiteralContext } from "./esql_parser"; +import { ConstantDefaultContext } from "./esql_parser"; +import { DereferenceContext } from "./esql_parser"; +import { ParenthesizedExpressionContext } from "./esql_parser"; +import { FunctionExpressionContext } from "./esql_parser"; +import { SingleCommandQueryContext } from "./esql_parser"; +import { CompositeQueryContext } from "./esql_parser"; +import { LogicalNotContext } from "./esql_parser"; +import { BooleanDefaultContext } from "./esql_parser"; +import { LogicalBinaryContext } from "./esql_parser"; +import { OperatorExpressionDefaultContext } from "./esql_parser"; +import { ArithmeticUnaryContext } from "./esql_parser"; +import { ArithmeticBinaryContext } from "./esql_parser"; +import { SingleStatementContext } from "./esql_parser"; +import { QueryContext } from "./esql_parser"; +import { SourceCommandContext } from "./esql_parser"; +import { ProcessingCommandContext } from "./esql_parser"; +import { WhereCommandContext } from "./esql_parser"; +import { BooleanExpressionContext } from "./esql_parser"; +import { ValueExpressionContext } from "./esql_parser"; +import { OperatorExpressionContext } from "./esql_parser"; +import { PrimaryExpressionContext } from "./esql_parser"; +import { RowCommandContext } from "./esql_parser"; +import { FieldsContext } from "./esql_parser"; +import { FieldContext } from "./esql_parser"; +import { FromCommandContext } from "./esql_parser"; +import { EvalCommandContext } from "./esql_parser"; +import { StatsCommandContext } from "./esql_parser"; +import { SourceIdentifierContext } from "./esql_parser"; +import { FunctionExpressionArgumentContext } from "./esql_parser"; +import { QualifiedNameContext } from "./esql_parser"; +import { QualifiedNamesContext } from "./esql_parser"; +import { IdentifierContext } from "./esql_parser"; +import { FunctionIdentifierContext } from "./esql_parser"; +import { ConstantContext } from "./esql_parser"; +import { LimitCommandContext } from "./esql_parser"; +import { SortCommandContext } from "./esql_parser"; +import { OrderExpressionContext } from "./esql_parser"; +import { ProjectCommandContext } from "./esql_parser"; +import { ProjectClauseContext } from "./esql_parser"; +import { BooleanValueContext } from "./esql_parser"; +import { NumberContext } from "./esql_parser"; +import { StringContext } from "./esql_parser"; +import { ComparisonOperatorContext } from "./esql_parser"; +import { ExplainCommandContext } from "./esql_parser"; +import { SubqueryExpressionContext } from "./esql_parser"; + + +/** + * This interface defines a complete listener for a parse tree produced by + * `esql_parser`. + */ +export interface esql_parserListener extends ParseTreeListener { + /** + * Enter a parse tree produced by the `valueFunctionExpression` + * labeled alternative in `esql_parser.valueExpression`. + * @param ctx the parse tree + */ + enterValueFunctionExpression?: (ctx: ValueFunctionExpressionContext) => void; + /** + * Exit a parse tree produced by the `valueFunctionExpression` + * labeled alternative in `esql_parser.valueExpression`. + * @param ctx the parse tree + */ + exitValueFunctionExpression?: (ctx: ValueFunctionExpressionContext) => void; + + /** + * Enter a parse tree produced by the `valueExpressionDefault` + * labeled alternative in `esql_parser.valueExpression`. + * @param ctx the parse tree + */ + enterValueExpressionDefault?: (ctx: ValueExpressionDefaultContext) => void; + /** + * Exit a parse tree produced by the `valueExpressionDefault` + * labeled alternative in `esql_parser.valueExpression`. + * @param ctx the parse tree + */ + exitValueExpressionDefault?: (ctx: ValueExpressionDefaultContext) => void; + + /** + * Enter a parse tree produced by the `comparison` + * labeled alternative in `esql_parser.valueExpression`. + * @param ctx the parse tree + */ + enterComparison?: (ctx: ComparisonContext) => void; + /** + * Exit a parse tree produced by the `comparison` + * labeled alternative in `esql_parser.valueExpression`. + * @param ctx the parse tree + */ + exitComparison?: (ctx: ComparisonContext) => void; + + /** + * Enter a parse tree produced by the `nullLiteral` + * labeled alternative in `esql_parser.constant`. + * @param ctx the parse tree + */ + enterNullLiteral?: (ctx: NullLiteralContext) => void; + /** + * Exit a parse tree produced by the `nullLiteral` + * labeled alternative in `esql_parser.constant`. + * @param ctx the parse tree + */ + exitNullLiteral?: (ctx: NullLiteralContext) => void; + + /** + * Enter a parse tree produced by the `numericLiteral` + * labeled alternative in `esql_parser.constant`. + * @param ctx the parse tree + */ + enterNumericLiteral?: (ctx: NumericLiteralContext) => void; + /** + * Exit a parse tree produced by the `numericLiteral` + * labeled alternative in `esql_parser.constant`. + * @param ctx the parse tree + */ + exitNumericLiteral?: (ctx: NumericLiteralContext) => void; + + /** + * Enter a parse tree produced by the `booleanLiteral` + * labeled alternative in `esql_parser.constant`. + * @param ctx the parse tree + */ + enterBooleanLiteral?: (ctx: BooleanLiteralContext) => void; + /** + * Exit a parse tree produced by the `booleanLiteral` + * labeled alternative in `esql_parser.constant`. + * @param ctx the parse tree + */ + exitBooleanLiteral?: (ctx: BooleanLiteralContext) => void; + + /** + * Enter a parse tree produced by the `stringLiteral` + * labeled alternative in `esql_parser.constant`. + * @param ctx the parse tree + */ + enterStringLiteral?: (ctx: StringLiteralContext) => void; + /** + * Exit a parse tree produced by the `stringLiteral` + * labeled alternative in `esql_parser.constant`. + * @param ctx the parse tree + */ + exitStringLiteral?: (ctx: StringLiteralContext) => void; + + /** + * Enter a parse tree produced by the `decimalLiteral` + * labeled alternative in `esql_parser.number`. + * @param ctx the parse tree + */ + enterDecimalLiteral?: (ctx: DecimalLiteralContext) => void; + /** + * Exit a parse tree produced by the `decimalLiteral` + * labeled alternative in `esql_parser.number`. + * @param ctx the parse tree + */ + exitDecimalLiteral?: (ctx: DecimalLiteralContext) => void; + + /** + * Enter a parse tree produced by the `integerLiteral` + * labeled alternative in `esql_parser.number`. + * @param ctx the parse tree + */ + enterIntegerLiteral?: (ctx: IntegerLiteralContext) => void; + /** + * Exit a parse tree produced by the `integerLiteral` + * labeled alternative in `esql_parser.number`. + * @param ctx the parse tree + */ + exitIntegerLiteral?: (ctx: IntegerLiteralContext) => void; + + /** + * Enter a parse tree produced by the `constantDefault` + * labeled alternative in `esql_parser.primaryExpression`. + * @param ctx the parse tree + */ + enterConstantDefault?: (ctx: ConstantDefaultContext) => void; + /** + * Exit a parse tree produced by the `constantDefault` + * labeled alternative in `esql_parser.primaryExpression`. + * @param ctx the parse tree + */ + exitConstantDefault?: (ctx: ConstantDefaultContext) => void; + + /** + * Enter a parse tree produced by the `dereference` + * labeled alternative in `esql_parser.primaryExpression`. + * @param ctx the parse tree + */ + enterDereference?: (ctx: DereferenceContext) => void; + /** + * Exit a parse tree produced by the `dereference` + * labeled alternative in `esql_parser.primaryExpression`. + * @param ctx the parse tree + */ + exitDereference?: (ctx: DereferenceContext) => void; + + /** + * Enter a parse tree produced by the `parenthesizedExpression` + * labeled alternative in `esql_parser.primaryExpression`. + * @param ctx the parse tree + */ + enterParenthesizedExpression?: (ctx: ParenthesizedExpressionContext) => void; + /** + * Exit a parse tree produced by the `parenthesizedExpression` + * labeled alternative in `esql_parser.primaryExpression`. + * @param ctx the parse tree + */ + exitParenthesizedExpression?: (ctx: ParenthesizedExpressionContext) => void; + + /** + * Enter a parse tree produced by the `functionExpression` + * labeled alternative in `esql_parser.primaryExpression`. + * @param ctx the parse tree + */ + enterFunctionExpression?: (ctx: FunctionExpressionContext) => void; + /** + * Exit a parse tree produced by the `functionExpression` + * labeled alternative in `esql_parser.primaryExpression`. + * @param ctx the parse tree + */ + exitFunctionExpression?: (ctx: FunctionExpressionContext) => void; + + /** + * Enter a parse tree produced by the `singleCommandQuery` + * labeled alternative in `esql_parser.query`. + * @param ctx the parse tree + */ + enterSingleCommandQuery?: (ctx: SingleCommandQueryContext) => void; + /** + * Exit a parse tree produced by the `singleCommandQuery` + * labeled alternative in `esql_parser.query`. + * @param ctx the parse tree + */ + exitSingleCommandQuery?: (ctx: SingleCommandQueryContext) => void; + + /** + * Enter a parse tree produced by the `compositeQuery` + * labeled alternative in `esql_parser.query`. + * @param ctx the parse tree + */ + enterCompositeQuery?: (ctx: CompositeQueryContext) => void; + /** + * Exit a parse tree produced by the `compositeQuery` + * labeled alternative in `esql_parser.query`. + * @param ctx the parse tree + */ + exitCompositeQuery?: (ctx: CompositeQueryContext) => void; + + /** + * Enter a parse tree produced by the `logicalNot` + * labeled alternative in `esql_parser.booleanExpression`. + * @param ctx the parse tree + */ + enterLogicalNot?: (ctx: LogicalNotContext) => void; + /** + * Exit a parse tree produced by the `logicalNot` + * labeled alternative in `esql_parser.booleanExpression`. + * @param ctx the parse tree + */ + exitLogicalNot?: (ctx: LogicalNotContext) => void; + + /** + * Enter a parse tree produced by the `booleanDefault` + * labeled alternative in `esql_parser.booleanExpression`. + * @param ctx the parse tree + */ + enterBooleanDefault?: (ctx: BooleanDefaultContext) => void; + /** + * Exit a parse tree produced by the `booleanDefault` + * labeled alternative in `esql_parser.booleanExpression`. + * @param ctx the parse tree + */ + exitBooleanDefault?: (ctx: BooleanDefaultContext) => void; + + /** + * Enter a parse tree produced by the `logicalBinary` + * labeled alternative in `esql_parser.booleanExpression`. + * @param ctx the parse tree + */ + enterLogicalBinary?: (ctx: LogicalBinaryContext) => void; + /** + * Exit a parse tree produced by the `logicalBinary` + * labeled alternative in `esql_parser.booleanExpression`. + * @param ctx the parse tree + */ + exitLogicalBinary?: (ctx: LogicalBinaryContext) => void; + + /** + * Enter a parse tree produced by the `operatorExpressionDefault` + * labeled alternative in `esql_parser.operatorExpression`. + * @param ctx the parse tree + */ + enterOperatorExpressionDefault?: (ctx: OperatorExpressionDefaultContext) => void; + /** + * Exit a parse tree produced by the `operatorExpressionDefault` + * labeled alternative in `esql_parser.operatorExpression`. + * @param ctx the parse tree + */ + exitOperatorExpressionDefault?: (ctx: OperatorExpressionDefaultContext) => void; + + /** + * Enter a parse tree produced by the `arithmeticUnary` + * labeled alternative in `esql_parser.operatorExpression`. + * @param ctx the parse tree + */ + enterArithmeticUnary?: (ctx: ArithmeticUnaryContext) => void; + /** + * Exit a parse tree produced by the `arithmeticUnary` + * labeled alternative in `esql_parser.operatorExpression`. + * @param ctx the parse tree + */ + exitArithmeticUnary?: (ctx: ArithmeticUnaryContext) => void; + + /** + * Enter a parse tree produced by the `arithmeticBinary` + * labeled alternative in `esql_parser.operatorExpression`. + * @param ctx the parse tree + */ + enterArithmeticBinary?: (ctx: ArithmeticBinaryContext) => void; + /** + * Exit a parse tree produced by the `arithmeticBinary` + * labeled alternative in `esql_parser.operatorExpression`. + * @param ctx the parse tree + */ + exitArithmeticBinary?: (ctx: ArithmeticBinaryContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.singleStatement`. + * @param ctx the parse tree + */ + enterSingleStatement?: (ctx: SingleStatementContext) => void; + /** + * Exit a parse tree produced by `esql_parser.singleStatement`. + * @param ctx the parse tree + */ + exitSingleStatement?: (ctx: SingleStatementContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.query`. + * @param ctx the parse tree + */ + enterQuery?: (ctx: QueryContext) => void; + /** + * Exit a parse tree produced by `esql_parser.query`. + * @param ctx the parse tree + */ + exitQuery?: (ctx: QueryContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.sourceCommand`. + * @param ctx the parse tree + */ + enterSourceCommand?: (ctx: SourceCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.sourceCommand`. + * @param ctx the parse tree + */ + exitSourceCommand?: (ctx: SourceCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.processingCommand`. + * @param ctx the parse tree + */ + enterProcessingCommand?: (ctx: ProcessingCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.processingCommand`. + * @param ctx the parse tree + */ + exitProcessingCommand?: (ctx: ProcessingCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.whereCommand`. + * @param ctx the parse tree + */ + enterWhereCommand?: (ctx: WhereCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.whereCommand`. + * @param ctx the parse tree + */ + exitWhereCommand?: (ctx: WhereCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.booleanExpression`. + * @param ctx the parse tree + */ + enterBooleanExpression?: (ctx: BooleanExpressionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.booleanExpression`. + * @param ctx the parse tree + */ + exitBooleanExpression?: (ctx: BooleanExpressionContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.valueExpression`. + * @param ctx the parse tree + */ + enterValueExpression?: (ctx: ValueExpressionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.valueExpression`. + * @param ctx the parse tree + */ + exitValueExpression?: (ctx: ValueExpressionContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.operatorExpression`. + * @param ctx the parse tree + */ + enterOperatorExpression?: (ctx: OperatorExpressionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.operatorExpression`. + * @param ctx the parse tree + */ + exitOperatorExpression?: (ctx: OperatorExpressionContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.primaryExpression`. + * @param ctx the parse tree + */ + enterPrimaryExpression?: (ctx: PrimaryExpressionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.primaryExpression`. + * @param ctx the parse tree + */ + exitPrimaryExpression?: (ctx: PrimaryExpressionContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.rowCommand`. + * @param ctx the parse tree + */ + enterRowCommand?: (ctx: RowCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.rowCommand`. + * @param ctx the parse tree + */ + exitRowCommand?: (ctx: RowCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.fields`. + * @param ctx the parse tree + */ + enterFields?: (ctx: FieldsContext) => void; + /** + * Exit a parse tree produced by `esql_parser.fields`. + * @param ctx the parse tree + */ + exitFields?: (ctx: FieldsContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.field`. + * @param ctx the parse tree + */ + enterField?: (ctx: FieldContext) => void; + /** + * Exit a parse tree produced by `esql_parser.field`. + * @param ctx the parse tree + */ + exitField?: (ctx: FieldContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.fromCommand`. + * @param ctx the parse tree + */ + enterFromCommand?: (ctx: FromCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.fromCommand`. + * @param ctx the parse tree + */ + exitFromCommand?: (ctx: FromCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.evalCommand`. + * @param ctx the parse tree + */ + enterEvalCommand?: (ctx: EvalCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.evalCommand`. + * @param ctx the parse tree + */ + exitEvalCommand?: (ctx: EvalCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.statsCommand`. + * @param ctx the parse tree + */ + enterStatsCommand?: (ctx: StatsCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.statsCommand`. + * @param ctx the parse tree + */ + exitStatsCommand?: (ctx: StatsCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.sourceIdentifier`. + * @param ctx the parse tree + */ + enterSourceIdentifier?: (ctx: SourceIdentifierContext) => void; + /** + * Exit a parse tree produced by `esql_parser.sourceIdentifier`. + * @param ctx the parse tree + */ + exitSourceIdentifier?: (ctx: SourceIdentifierContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.functionExpressionArgument`. + * @param ctx the parse tree + */ + enterFunctionExpressionArgument?: (ctx: FunctionExpressionArgumentContext) => void; + /** + * Exit a parse tree produced by `esql_parser.functionExpressionArgument`. + * @param ctx the parse tree + */ + exitFunctionExpressionArgument?: (ctx: FunctionExpressionArgumentContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.qualifiedName`. + * @param ctx the parse tree + */ + enterQualifiedName?: (ctx: QualifiedNameContext) => void; + /** + * Exit a parse tree produced by `esql_parser.qualifiedName`. + * @param ctx the parse tree + */ + exitQualifiedName?: (ctx: QualifiedNameContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.qualifiedNames`. + * @param ctx the parse tree + */ + enterQualifiedNames?: (ctx: QualifiedNamesContext) => void; + /** + * Exit a parse tree produced by `esql_parser.qualifiedNames`. + * @param ctx the parse tree + */ + exitQualifiedNames?: (ctx: QualifiedNamesContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.identifier`. + * @param ctx the parse tree + */ + enterIdentifier?: (ctx: IdentifierContext) => void; + /** + * Exit a parse tree produced by `esql_parser.identifier`. + * @param ctx the parse tree + */ + exitIdentifier?: (ctx: IdentifierContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.functionIdentifier`. + * @param ctx the parse tree + */ + enterFunctionIdentifier?: (ctx: FunctionIdentifierContext) => void; + /** + * Exit a parse tree produced by `esql_parser.functionIdentifier`. + * @param ctx the parse tree + */ + exitFunctionIdentifier?: (ctx: FunctionIdentifierContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.constant`. + * @param ctx the parse tree + */ + enterConstant?: (ctx: ConstantContext) => void; + /** + * Exit a parse tree produced by `esql_parser.constant`. + * @param ctx the parse tree + */ + exitConstant?: (ctx: ConstantContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.limitCommand`. + * @param ctx the parse tree + */ + enterLimitCommand?: (ctx: LimitCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.limitCommand`. + * @param ctx the parse tree + */ + exitLimitCommand?: (ctx: LimitCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.sortCommand`. + * @param ctx the parse tree + */ + enterSortCommand?: (ctx: SortCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.sortCommand`. + * @param ctx the parse tree + */ + exitSortCommand?: (ctx: SortCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.orderExpression`. + * @param ctx the parse tree + */ + enterOrderExpression?: (ctx: OrderExpressionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.orderExpression`. + * @param ctx the parse tree + */ + exitOrderExpression?: (ctx: OrderExpressionContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.projectCommand`. + * @param ctx the parse tree + */ + enterProjectCommand?: (ctx: ProjectCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.projectCommand`. + * @param ctx the parse tree + */ + exitProjectCommand?: (ctx: ProjectCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.projectClause`. + * @param ctx the parse tree + */ + enterProjectClause?: (ctx: ProjectClauseContext) => void; + /** + * Exit a parse tree produced by `esql_parser.projectClause`. + * @param ctx the parse tree + */ + exitProjectClause?: (ctx: ProjectClauseContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.booleanValue`. + * @param ctx the parse tree + */ + enterBooleanValue?: (ctx: BooleanValueContext) => void; + /** + * Exit a parse tree produced by `esql_parser.booleanValue`. + * @param ctx the parse tree + */ + exitBooleanValue?: (ctx: BooleanValueContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.number`. + * @param ctx the parse tree + */ + enterNumber?: (ctx: NumberContext) => void; + /** + * Exit a parse tree produced by `esql_parser.number`. + * @param ctx the parse tree + */ + exitNumber?: (ctx: NumberContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.string`. + * @param ctx the parse tree + */ + enterString?: (ctx: StringContext) => void; + /** + * Exit a parse tree produced by `esql_parser.string`. + * @param ctx the parse tree + */ + exitString?: (ctx: StringContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.comparisonOperator`. + * @param ctx the parse tree + */ + enterComparisonOperator?: (ctx: ComparisonOperatorContext) => void; + /** + * Exit a parse tree produced by `esql_parser.comparisonOperator`. + * @param ctx the parse tree + */ + exitComparisonOperator?: (ctx: ComparisonOperatorContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.explainCommand`. + * @param ctx the parse tree + */ + enterExplainCommand?: (ctx: ExplainCommandContext) => void; + /** + * Exit a parse tree produced by `esql_parser.explainCommand`. + * @param ctx the parse tree + */ + exitExplainCommand?: (ctx: ExplainCommandContext) => void; + + /** + * Enter a parse tree produced by `esql_parser.subqueryExpression`. + * @param ctx the parse tree + */ + enterSubqueryExpression?: (ctx: SubqueryExpressionContext) => void; + /** + * Exit a parse tree produced by `esql_parser.subqueryExpression`. + * @param ctx the parse tree + */ + exitSubqueryExpression?: (ctx: SubqueryExpressionContext) => void; +} + diff --git a/packages/kbn-monaco/src/esql/index.ts b/packages/kbn-monaco/src/esql/index.ts index 9f0bbb7b64c7d..e34fb4917fe02 100644 --- a/packages/kbn-monaco/src/esql/index.ts +++ b/packages/kbn-monaco/src/esql/index.ts @@ -6,8 +6,7 @@ * Side Public License, v 1. */ -import { LangModuleType } from '../types'; -import { ID } from './constants'; -import { lexerRules } from './lexer_rules'; +export { ESQL_LANG_ID, ESQL_THEME_ID } from './lib/constants'; +export { ESQLLang } from './language'; -export const EsqlLang: LangModuleType = { ID, lexerRules }; +export { buildESQlTheme } from './lib/monaco/esql_theme'; diff --git a/packages/kbn-monaco/src/esql/language.ts b/packages/kbn-monaco/src/esql/language.ts new file mode 100644 index 0000000000000..6da924ee2f0c7 --- /dev/null +++ b/packages/kbn-monaco/src/esql/language.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { monaco } from '../monaco_imports'; + +import { ESQL_LANG_ID } from './lib/constants'; + +import type { CustomLangModuleType } from '../types'; +import type { ESQLWorker } from './worker/esql_worker'; + +import { DiagnosticsAdapter } from '../common/diagnostics_adapter'; +import { WorkerProxyService } from '../common/worker_proxy'; + +export const ESQLLang: CustomLangModuleType = { + ID: ESQL_LANG_ID, + async onLanguage() { + const { ESQLTokensProvider } = await import('./lib/monaco'); + const workerProxyService = new WorkerProxyService(); + + workerProxyService.setup(ESQL_LANG_ID); + + monaco.languages.setTokensProvider(ESQL_LANG_ID, new ESQLTokensProvider()); + + new DiagnosticsAdapter(ESQL_LANG_ID, (...uris) => workerProxyService.getWorker(uris)); + }, +}; diff --git a/packages/kbn-monaco/src/esql/lib/antlr_facade.ts b/packages/kbn-monaco/src/esql/lib/antlr_facade.ts new file mode 100644 index 0000000000000..d8c0c1f6e87e2 --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/antlr_facade.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CommonTokenStream, CodePointCharStream } from 'antlr4ts'; + +import { esql_lexer as ESQLLexer } from '../antlr/esql_lexer'; +import { esql_parser as ESQLParser } from '../antlr/esql_parser'; + +import type { ANTLREErrorListener } from '../../common/error_listener'; + +export const getParser = (inputStream: CodePointCharStream, errorListener: ANTLREErrorListener) => { + const lexer = getLexer(inputStream, errorListener); + const tokenStream = new CommonTokenStream(lexer); + const parser = new ESQLParser(tokenStream); + + parser.removeErrorListeners(); + parser.addErrorListener(errorListener); + + return parser; +}; + +export const getLexer = (inputStream: CodePointCharStream, errorListener: ANTLREErrorListener) => { + const lexer = new ESQLLexer(inputStream); + + lexer.removeErrorListeners(); + lexer.addErrorListener(errorListener); + + return lexer; +}; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/types/pie.js b/packages/kbn-monaco/src/esql/lib/constants.ts similarity index 58% rename from src/plugins/vis_types/vislib/public/vislib/lib/types/pie.js rename to packages/kbn-monaco/src/esql/lib/constants.ts index f65cf621f3a82..0a12188bfaee5 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/types/pie.js +++ b/packages/kbn-monaco/src/esql/lib/constants.ts @@ -6,17 +6,7 @@ * Side Public License, v 1. */ -import _ from 'lodash'; +export const ESQL_LANG_ID = 'esql'; +export const ESQL_THEME_ID = 'esqlTheme'; -export function vislibPieConfig(config) { - if (!config.chart) { - config.chart = _.defaults({}, config, { - type: 'pie', - labels: { - show: false, - truncate: 100, - }, - }); - } - return config; -} +export const ESQL_TOKEN_POSTFIX = '.esql'; diff --git a/packages/kbn-monaco/src/esql/lib/monaco/esql_line_tokens.ts b/packages/kbn-monaco/src/esql/lib/monaco/esql_line_tokens.ts new file mode 100644 index 0000000000000..c4817aac586d4 --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/monaco/esql_line_tokens.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { monaco } from '../../../monaco_imports'; +import { ESQLState } from './esql_state'; + +/** @internal **/ +export class ESQLLineTokens implements monaco.languages.ILineTokens { + endState: monaco.languages.IState; + tokens: monaco.languages.IToken[]; + + constructor(tokens: monaco.languages.IToken[]) { + this.endState = new ESQLState(); + this.tokens = tokens; + } +} diff --git a/test/functional/apps/dashboard/group6/config.ts b/packages/kbn-monaco/src/esql/lib/monaco/esql_state.ts similarity index 53% rename from test/functional/apps/dashboard/group6/config.ts rename to packages/kbn-monaco/src/esql/lib/monaco/esql_state.ts index a70a190ca63f8..a7cfd10f79276 100644 --- a/test/functional/apps/dashboard/group6/config.ts +++ b/packages/kbn-monaco/src/esql/lib/monaco/esql_state.ts @@ -6,13 +6,15 @@ * Side Public License, v 1. */ -import { FtrConfigProviderContext } from '@kbn/test'; +import { monaco } from '../../../monaco_imports'; -export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); +/** @internal **/ +export class ESQLState implements monaco.languages.IState { + clone(): monaco.languages.IState { + return new ESQLState(); + } - return { - ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], - }; + equals(other: monaco.languages.IState): boolean { + return false; + } } diff --git a/packages/kbn-monaco/src/esql/lib/monaco/esql_theme.ts b/packages/kbn-monaco/src/esql/lib/monaco/esql_theme.ts new file mode 100644 index 0000000000000..7e150ddc74872 --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/monaco/esql_theme.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { euiThemeVars, darkMode } from '@kbn/ui-theme'; +import { ESQL_TOKEN_POSTFIX } from '../constants'; +import { monaco } from '../../../monaco_imports'; + +const buildRuleGroup = (tokens: string[], color: string, isBold: boolean = false) => + tokens.map((i) => ({ + token: i + ESQL_TOKEN_POSTFIX, + foreground: color, + fontStyle: isBold ? 'bold' : '', + })); + +export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ + base: darkMode ? 'vs-dark' : 'vs', + inherit: true, + rules: [ + // base + ...buildRuleGroup( + [ + 'explain', + 'row', + 'limit', + 'project', + 'ws', + 'assign', + 'comma', + 'dot', + 'first', + 'last', + 'opening_bracket', + 'closing_bracket', + 'quoted_identifier', + 'src_ws', + 'unquoted_identifier', + ], + euiThemeVars.euiTextColor + ), + + // commands + ...buildRuleGroup( + [ + 'from', + 'stats', + 'eval', + 'sort', + 'by', + 'where', + 'unknown_cmd', + 'expr_ws', + 'row', + 'limit', + 'asc', + 'desc', + ], + euiThemeVars.euiColorPrimaryText + ), + + // math functions + ...buildRuleGroup( + [ + 'round_function_math', + 'avg_function_math', + 'sum_function_math', + 'min_function_math', + 'max_function_math', + ], + euiThemeVars.euiColorPrimaryText + ), + + // values + ...buildRuleGroup( + [ + 'pipe', + 'true', + 'not', + 'null', + 'nulls', + 'false', + 'src_unquoted_identifier', + 'src_quoted_identifier', + 'string', + ], + euiThemeVars.euiTextColor + ), + + // values #2 + ...buildRuleGroup( + [ + 'true', + 'not', + 'null', + 'nulls', + 'false', + 'not', + 'null', + 'percent', + 'integer_literal', + 'decimal_literal', + ], + euiThemeVars.euiTextColor + ), + + // operators + ...buildRuleGroup( + [ + 'or', + 'and', + 'rp', + 'eq', + 'neq', + 'lp', + 'lt', + 'lte', + 'gt', + 'gte', + 'plus', + 'minus', + 'asterisk', + 'slash', + ], + euiThemeVars.euiTextSubduedColor + ), + + // comments + ...buildRuleGroup( + [ + 'line_comment', + 'multiline_comment', + 'expr_line_comment', + 'expr_multiline_comment', + 'src_line_comment', + 'src_multiline_comment', + ], + euiThemeVars.euiColorMediumShade + ), + ], + colors: {}, +}); diff --git a/packages/kbn-monaco/src/esql/lib/monaco/esql_token.ts b/packages/kbn-monaco/src/esql/lib/monaco/esql_token.ts new file mode 100644 index 0000000000000..82058e68788f8 --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/monaco/esql_token.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { monaco } from '../../../monaco_imports'; +import { ESQL_TOKEN_POSTFIX } from '../constants'; + +/** @internal **/ +export class ESQLToken implements monaco.languages.IToken { + scopes: string; + + constructor(ruleName: string, public startIndex: number, public stopIndex?: number) { + this.scopes = ruleName.toLowerCase() + ESQL_TOKEN_POSTFIX; + } +} diff --git a/packages/kbn-monaco/src/esql/lib/monaco/esql_tokens_provider.ts b/packages/kbn-monaco/src/esql/lib/monaco/esql_tokens_provider.ts new file mode 100644 index 0000000000000..2166a5e6f68ea --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/monaco/esql_tokens_provider.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CharStreams, Token } from 'antlr4ts'; +import { monaco } from '../../../monaco_imports'; +import { ANTLREErrorListener } from '../../../common/error_listener'; + +import { ESQLToken } from './esql_token'; +import { ESQLLineTokens } from './esql_line_tokens'; +import { ESQLState } from './esql_state'; + +import { getLexer } from '../antlr_facade'; +import { ESQL_TOKEN_POSTFIX } from '../constants'; + +const EOF = -1; + +export class ESQLTokensProvider implements monaco.languages.TokensProvider { + getInitialState(): monaco.languages.IState { + return new ESQLState(); + } + + tokenize(line: string, state: monaco.languages.IState): monaco.languages.ILineTokens { + const errorStartingPoints: number[] = []; + const errorListener = new ANTLREErrorListener(); + const inputStream = CharStreams.fromString(line); + const lexer = getLexer(inputStream, errorListener); + + let done = false; + const myTokens: monaco.languages.IToken[] = []; + + do { + let token: Token | null; + try { + token = lexer.nextToken(); + } catch (e) { + token = null; + } + + if (token == null) { + done = true; + } else { + if (token.type === EOF) { + done = true; + } else { + const tokenTypeName = lexer.vocabulary.getSymbolicName(token.type); + + if (tokenTypeName) { + const myToken = new ESQLToken(tokenTypeName, token.startIndex, token.stopIndex); + myTokens.push(myToken); + } + } + } + } while (!done); + + for (const e of errorStartingPoints) { + myTokens.push(new ESQLToken('error' + ESQL_TOKEN_POSTFIX, e)); + } + + myTokens.sort((a, b) => a.startIndex - b.startIndex); + + return new ESQLLineTokens(myTokens); + } +} diff --git a/packages/kbn-monaco/src/esql/lib/monaco/index.ts b/packages/kbn-monaco/src/esql/lib/monaco/index.ts new file mode 100644 index 0000000000000..6fb59f106e83b --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/monaco/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { ESQLTokensProvider } from './esql_tokens_provider'; diff --git a/packages/kbn-monaco/src/esql/worker/esql.worker.ts b/packages/kbn-monaco/src/esql/worker/esql.worker.ts new file mode 100644 index 0000000000000..20af6bf9c5c04 --- /dev/null +++ b/packages/kbn-monaco/src/esql/worker/esql.worker.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 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. + */ + +// This module is intended to be run inside of a webworker +/* eslint-disable @kbn/eslint/module_migration */ + +import '@babel/runtime/regenerator'; + +// @ts-ignore +import * as worker from 'monaco-editor/esm/vs/editor/editor.worker'; + +import { monaco } from '../../monaco_imports'; +import { ESQLWorker } from './esql_worker'; + +self.onmessage = () => { + worker.initialize((ctx: monaco.worker.IWorkerContext, createData: any) => { + return new ESQLWorker(ctx); + }); +}; diff --git a/packages/kbn-monaco/src/esql/worker/esql_worker.ts b/packages/kbn-monaco/src/esql/worker/esql_worker.ts new file mode 100644 index 0000000000000..c83d8707dac69 --- /dev/null +++ b/packages/kbn-monaco/src/esql/worker/esql_worker.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CharStreams } from 'antlr4ts'; +import { monaco } from '../../monaco_imports'; +import type { BaseWorkerDefinition } from '../../types'; +import { getParser } from '../lib/antlr_facade'; +import { ANTLREErrorListener } from '../../common/error_listener'; + +export class ESQLWorker implements BaseWorkerDefinition { + private readonly _ctx: monaco.worker.IWorkerContext; + + constructor(ctx: monaco.worker.IWorkerContext) { + this._ctx = ctx; + } + + private getTextDocument(modelUri: string): string | undefined { + const model = this._ctx.getMirrorModels().find((m) => m.uri.toString() === modelUri); + + return model?.getValue(); + } + + public async getSyntaxErrors(modelUri: string) { + const code = this.getTextDocument(modelUri); + + if (code) { + const inputStream = CharStreams.fromString(code); + const errorListener = new ANTLREErrorListener(); + const parser = getParser(inputStream, errorListener); + + parser.singleStatement(); + + return errorListener.getErrors(); + } + } +} diff --git a/packages/kbn-monaco/src/helpers.ts b/packages/kbn-monaco/src/helpers.ts index defdd00d6fdc1..81d30b680762a 100644 --- a/packages/kbn-monaco/src/helpers.ts +++ b/packages/kbn-monaco/src/helpers.ts @@ -6,18 +6,32 @@ * Side Public License, v 1. */ import { monaco } from './monaco_imports'; -import { LangModuleType } from './types'; +import type { LangModuleType, CustomLangModuleType } from './types'; -function registerLanguage(language: LangModuleType) { +export function registerLanguage(language: LangModuleType | CustomLangModuleType) { const { ID, lexerRules, languageConfiguration } = language; monaco.languages.register({ id: ID }); - monaco.languages.onLanguage(ID, () => { - monaco.languages.setMonarchTokensProvider(ID, lexerRules); + + monaco.languages.onLanguage(ID, async () => { + if (lexerRules) { + monaco.languages.setMonarchTokensProvider(ID, lexerRules); + } + if (languageConfiguration) { monaco.languages.setLanguageConfiguration(ID, languageConfiguration); } + + if ('onLanguage' in language) { + await language.onLanguage(); + } }); } -export { registerLanguage }; +export function registerTheme(id: string, themeData: monaco.editor.IStandaloneThemeData) { + try { + monaco.editor.defineTheme(id, themeData); + } catch (e) { + // nothing to be here + } +} diff --git a/packages/kbn-monaco/src/painless/completion_adapter.ts b/packages/kbn-monaco/src/painless/completion_adapter.ts index f8f1c8c252e8d..e84d5754ad60e 100644 --- a/packages/kbn-monaco/src/painless/completion_adapter.ts +++ b/packages/kbn-monaco/src/painless/completion_adapter.ts @@ -8,8 +8,8 @@ import { monaco } from '../monaco_imports'; import { EditorStateService } from './lib'; -import { PainlessCompletionResult, PainlessCompletionKind } from './types'; -import { PainlessWorker } from './worker'; +import type { PainlessCompletionResult, PainlessCompletionKind } from './types'; +import type { PainlessWorker } from './worker'; const getCompletionKind = (kind: PainlessCompletionKind): monaco.languages.CompletionItemKind => { const monacoItemKind = monaco.languages.CompletionItemKind; diff --git a/packages/kbn-monaco/src/painless/index.ts b/packages/kbn-monaco/src/painless/index.ts index 793dc5142a41e..52ab2a4dd4a70 100644 --- a/packages/kbn-monaco/src/painless/index.ts +++ b/packages/kbn-monaco/src/painless/index.ts @@ -9,7 +9,7 @@ import { ID } from './constants'; import { lexerRules, languageConfiguration } from './lexer_rules'; import { getSuggestionProvider, getSyntaxErrors, validation$ } from './language'; -import { CompleteLangModuleType } from '../types'; +import type { CompleteLangModuleType } from '../types'; export const PainlessLang: CompleteLangModuleType = { ID, diff --git a/packages/kbn-monaco/src/painless/language.ts b/packages/kbn-monaco/src/painless/language.ts index abeee8d501f31..5250719cb08db 100644 --- a/packages/kbn-monaco/src/painless/language.ts +++ b/packages/kbn-monaco/src/painless/language.ts @@ -7,21 +7,20 @@ */ import { Observable, of } from 'rxjs'; import { monaco } from '../monaco_imports'; - -import { WorkerProxyService, EditorStateService } from './lib'; -import { LangValidation, SyntaxErrors } from '../types'; import { ID } from './constants'; -import { PainlessContext, PainlessAutocompleteField } from './types'; -import { PainlessWorker } from './worker'; + +import type { LangValidation, SyntaxErrors } from '../types'; +import type { PainlessContext, PainlessAutocompleteField } from './types'; +import type { PainlessWorker } from './worker'; +import { EditorStateService } from './lib'; import { PainlessCompletionAdapter } from './completion_adapter'; -import { DiagnosticsAdapter } from './diagnostics_adapter'; +import { DiagnosticsAdapter } from '../common/diagnostics_adapter'; +import { WorkerProxyService } from '../common/worker_proxy'; -const workerProxyService = new WorkerProxyService(); +const workerProxyService = new WorkerProxyService(); const editorStateService = new EditorStateService(); -export type WorkerAccessor = (...uris: monaco.Uri[]) => Promise; - -const worker: WorkerAccessor = (...uris: monaco.Uri[]): Promise => { +const worker = (...uris: monaco.Uri[]): Promise => { return workerProxyService.getWorker(uris); }; @@ -46,7 +45,7 @@ export const validation$: () => Observable = () => of({ isValid: true, isValidating: false, errors: [] }); monaco.languages.onLanguage(ID, async () => { - workerProxyService.setup(); + workerProxyService.setup(ID); - diagnosticsAdapter = new DiagnosticsAdapter(worker); + diagnosticsAdapter = new DiagnosticsAdapter(ID, worker); }); diff --git a/packages/kbn-monaco/src/painless/lib/editor_state.ts b/packages/kbn-monaco/src/painless/lib/editor_state.ts index 6d64a128bdf21..731f1ab5c01db 100644 --- a/packages/kbn-monaco/src/painless/lib/editor_state.ts +++ b/packages/kbn-monaco/src/painless/lib/editor_state.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PainlessContext, PainlessAutocompleteField } from '../types'; +import type { PainlessContext, PainlessAutocompleteField } from '../types'; export interface EditorState { context: PainlessContext; diff --git a/packages/kbn-monaco/src/painless/lib/index.ts b/packages/kbn-monaco/src/painless/lib/index.ts index 402b582b7424f..274175a33896c 100644 --- a/packages/kbn-monaco/src/painless/lib/index.ts +++ b/packages/kbn-monaco/src/painless/lib/index.ts @@ -6,6 +6,5 @@ * Side Public License, v 1. */ -export type { EditorState } from './editor_state'; export { EditorStateService } from './editor_state'; -export { WorkerProxyService } from './worker_proxy'; +export type { EditorState } from './editor_state'; diff --git a/packages/kbn-monaco/src/painless/worker/index.ts b/packages/kbn-monaco/src/painless/worker/index.ts index fdbaa23f74bb1..309ae3c70154a 100644 --- a/packages/kbn-monaco/src/painless/worker/index.ts +++ b/packages/kbn-monaco/src/painless/worker/index.ts @@ -7,5 +7,3 @@ */ export { PainlessWorker } from './painless_worker'; - -export type { PainlessError } from './lib'; diff --git a/packages/kbn-monaco/src/painless/worker/lib/index.ts b/packages/kbn-monaco/src/painless/worker/lib/index.ts index 46f9be825800a..fc893c011db30 100644 --- a/packages/kbn-monaco/src/painless/worker/lib/index.ts +++ b/packages/kbn-monaco/src/painless/worker/lib/index.ts @@ -7,7 +7,4 @@ */ export { getAutocompleteSuggestions } from './autocomplete'; - -export type { PainlessError } from './error_listener'; - export { parseAndGetSyntaxErrors } from './parser'; diff --git a/packages/kbn-monaco/src/painless/worker/lib/parser.ts b/packages/kbn-monaco/src/painless/worker/lib/parser.ts index d7b91d3516f6c..3adf758e49084 100644 --- a/packages/kbn-monaco/src/painless/worker/lib/parser.ts +++ b/packages/kbn-monaco/src/painless/worker/lib/parser.ts @@ -8,18 +8,19 @@ import { CommonTokenStream, CharStreams } from 'antlr4ts'; import { painless_parser as PainlessParser, SourceContext } from '../../antlr/painless_parser'; -import { PainlessError, PainlessErrorListener } from './error_listener'; import { PainlessLexerEnhanced } from './lexer'; +import { EditorError } from '../../../types'; +import { ANTLREErrorListener } from '../../../common/error_listener'; const parse = ( code: string ): { source: SourceContext; - errors: PainlessError[]; + errors: EditorError[]; } => { const inputStream = CharStreams.fromString(code); const lexer = new PainlessLexerEnhanced(inputStream); - const painlessLangErrorListener = new PainlessErrorListener(); + const painlessLangErrorListener = new ANTLREErrorListener(); const tokenStream = new CommonTokenStream(lexer); const parser = new PainlessParser(tokenStream); @@ -29,7 +30,7 @@ const parse = ( lexer.addErrorListener(painlessLangErrorListener); parser.addErrorListener(painlessLangErrorListener); - const errors: PainlessError[] = painlessLangErrorListener.getErrors(); + const errors: EditorError[] = painlessLangErrorListener.getErrors(); return { source: parser.source(), @@ -37,7 +38,7 @@ const parse = ( }; }; -export const parseAndGetSyntaxErrors = (code: string): PainlessError[] => { +export const parseAndGetSyntaxErrors = (code: string): EditorError[] => { const { errors } = parse(code); return errors; }; diff --git a/packages/kbn-monaco/src/painless/worker/painless_worker.ts b/packages/kbn-monaco/src/painless/worker/painless_worker.ts index 9af28801db316..9b93d704e3748 100644 --- a/packages/kbn-monaco/src/painless/worker/painless_worker.ts +++ b/packages/kbn-monaco/src/painless/worker/painless_worker.ts @@ -7,10 +7,16 @@ */ import { monaco } from '../../monaco_imports'; -import { PainlessCompletionResult, PainlessContext, PainlessAutocompleteField } from '../types'; +import type { + PainlessCompletionResult, + PainlessContext, + PainlessAutocompleteField, +} from '../types'; +import type { BaseWorkerDefinition } from '../../types'; import { getAutocompleteSuggestions, parseAndGetSyntaxErrors } from './lib'; -export class PainlessWorker { + +export class PainlessWorker implements BaseWorkerDefinition { private _ctx: monaco.worker.IWorkerContext; constructor(ctx: monaco.worker.IWorkerContext) { diff --git a/packages/kbn-monaco/src/register_globals.ts b/packages/kbn-monaco/src/register_globals.ts index 8a69b05b9425f..21b4b8d92cf74 100644 --- a/packages/kbn-monaco/src/register_globals.ts +++ b/packages/kbn-monaco/src/register_globals.ts @@ -8,39 +8,58 @@ import { XJsonLang } from './xjson'; import { PainlessLang } from './painless'; -import { EsqlLang } from './esql'; +import { SQLLang } from './sql'; import { monaco } from './monaco_imports'; -import { registerLanguage } from './helpers'; +import { ESQL_THEME_ID, ESQLLang, buildESQlTheme } from './esql'; -import jsonWorkerSrc from '!!raw-loader!../../target_workers/json.editor.worker.js'; -import xJsonWorkerSrc from '!!raw-loader!../../target_workers/xjson.editor.worker.js'; -import defaultWorkerSrc from '!!raw-loader!../../target_workers/default.editor.worker.js'; -import painlessWorkerSrc from '!!raw-loader!../../target_workers/painless.editor.worker.js'; +import { registerLanguage, registerTheme } from './helpers'; +import { createWorkersRegistry } from './workers_registry'; + +export const DEFAULT_WORKER_ID = 'default'; + +const workerRegistry = createWorkersRegistry(DEFAULT_WORKER_ID); + +workerRegistry.register( + DEFAULT_WORKER_ID, + async () => await import('!!raw-loader!../../target_workers/default.editor.worker.js') +); + +workerRegistry.register( + XJsonLang.ID, + async () => await import('!!raw-loader!../../target_workers/xjson.editor.worker.js') +); + +workerRegistry.register( + PainlessLang.ID, + async () => await import('!!raw-loader!../../target_workers/painless.editor.worker.js') +); + +workerRegistry.register( + ESQLLang.ID, + async () => await import('!!raw-loader!../../target_workers/esql.editor.worker.js') +); + +workerRegistry.register( + monaco.languages.json.jsonDefaults.languageId, + async () => await import('!!raw-loader!../../target_workers/json.editor.worker.js') +); /** * Register languages and lexer rules */ registerLanguage(XJsonLang); registerLanguage(PainlessLang); -registerLanguage(EsqlLang); +registerLanguage(SQLLang); +registerLanguage(ESQLLang); /** - * Create web workers by language ID + * Register custom themes */ -const mapLanguageIdToWorker: { [key: string]: any } = { - [XJsonLang.ID]: xJsonWorkerSrc, - [PainlessLang.ID]: painlessWorkerSrc, - [monaco.languages.json.jsonDefaults.languageId]: jsonWorkerSrc, -}; +registerTheme(ESQL_THEME_ID, buildESQlTheme()); // @ts-ignore window.MonacoEnvironment = { // needed for functional tests so that we can get value from 'editor' monaco, - getWorker: (module: string, languageId: string) => { - const workerSrc = mapLanguageIdToWorker[languageId] || defaultWorkerSrc; - - const blob = new Blob([workerSrc], { type: 'application/javascript' }); - return new Worker(URL.createObjectURL(blob)); - }, + getWorker: workerRegistry.getWorker, }; diff --git a/packages/kbn-monaco/src/esql/constants.ts b/packages/kbn-monaco/src/sql/constants.ts similarity index 93% rename from packages/kbn-monaco/src/esql/constants.ts rename to packages/kbn-monaco/src/sql/constants.ts index ffd4fd1131ed7..4a774f2a9af16 100644 --- a/packages/kbn-monaco/src/esql/constants.ts +++ b/packages/kbn-monaco/src/sql/constants.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export const ID = 'esql'; +export const ID = 'sql'; diff --git a/packages/kbn-monaco/src/sql/index.ts b/packages/kbn-monaco/src/sql/index.ts new file mode 100644 index 0000000000000..c2a67cc7a627e --- /dev/null +++ b/packages/kbn-monaco/src/sql/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { LangModuleType } from '../types'; +import { ID } from './constants'; +import { lexerRules } from './lexer_rules'; + +export const SQLLang: LangModuleType = { ID, lexerRules }; diff --git a/packages/kbn-monaco/src/esql/lexer_rules/index.ts b/packages/kbn-monaco/src/sql/lexer_rules/index.ts similarity index 90% rename from packages/kbn-monaco/src/esql/lexer_rules/index.ts rename to packages/kbn-monaco/src/sql/lexer_rules/index.ts index 286abb5c7ca12..5b32abfde89a5 100644 --- a/packages/kbn-monaco/src/esql/lexer_rules/index.ts +++ b/packages/kbn-monaco/src/sql/lexer_rules/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { lexerRules } from './esql'; +export { lexerRules } from './sql'; diff --git a/packages/kbn-monaco/src/esql/lexer_rules/esql.ts b/packages/kbn-monaco/src/sql/lexer_rules/sql.ts similarity index 100% rename from packages/kbn-monaco/src/esql/lexer_rules/esql.ts rename to packages/kbn-monaco/src/sql/lexer_rules/sql.ts diff --git a/packages/kbn-monaco/src/types.ts b/packages/kbn-monaco/src/types.ts index 8512ef1ac58c0..380f76fb55ad3 100644 --- a/packages/kbn-monaco/src/types.ts +++ b/packages/kbn-monaco/src/types.ts @@ -5,16 +5,15 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import type { Observable } from 'rxjs'; import { monaco } from './monaco_imports'; export interface LangModuleType { ID: string; - lexerRules: monaco.languages.IMonarchLanguage; + lexerRules?: monaco.languages.IMonarchLanguage; languageConfiguration?: monaco.languages.LanguageConfiguration; - getSuggestionProvider?: Function; - getSyntaxErrors?: Function; } export interface CompleteLangModuleType extends LangModuleType { @@ -24,6 +23,10 @@ export interface CompleteLangModuleType extends LangModuleType { validation$: () => Observable; } +export interface CustomLangModuleType extends LangModuleType { + onLanguage: () => void; +} + export interface EditorError { startLineNumber: number; startColumn: number; @@ -41,3 +44,7 @@ export interface LangValidation { export interface SyntaxErrors { [modelId: string]: EditorError[]; } + +export interface BaseWorkerDefinition { + getSyntaxErrors: (modelUri: string) => Promise; +} diff --git a/packages/kbn-monaco/src/workers_registry.ts b/packages/kbn-monaco/src/workers_registry.ts new file mode 100644 index 0000000000000..474e531dee55b --- /dev/null +++ b/packages/kbn-monaco/src/workers_registry.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 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 createWorkersRegistry = (defaultWorkerId: string) => { + const registry = new Map Promise>(); + + return { + register: (languageId: string, getWorkerSrc: () => Promise) => { + registry.set(languageId, getWorkerSrc); + }, + + getWorker: async (module: string, languageId: string) => { + const getWorkerSrc = registry.get(languageId) || registry.get(defaultWorkerId); + if (getWorkerSrc) { + const src = await getWorkerSrc(); + + const blob = new Blob([src.default], { type: 'application/javascript' }); + return new Worker(URL.createObjectURL(blob)); + } else { + throw new Error(`Worker for ${languageId} is not registered`); + } + }, + }; +}; + +export type WorkersRegistry = ReturnType; diff --git a/packages/kbn-monaco/webpack.config.js b/packages/kbn-monaco/webpack.config.js index 8c6f82cdb21f5..15af5ad2f8e89 100644 --- a/packages/kbn-monaco/webpack.config.js +++ b/packages/kbn-monaco/webpack.config.js @@ -47,4 +47,4 @@ const getWorkerConfig = (language) => ({ }, }); -module.exports = ['default', 'json', 'painless', 'xjson'].map(getWorkerConfig); +module.exports = ['default', 'json', 'painless', 'xjson', 'esql'].map(getWorkerConfig); diff --git a/packages/kbn-storybook/src/lib/default_config.ts b/packages/kbn-storybook/src/lib/default_config.ts index a2712d3d6f24e..4e497572fdfd8 100644 --- a/packages/kbn-storybook/src/lib/default_config.ts +++ b/packages/kbn-storybook/src/lib/default_config.ts @@ -7,12 +7,16 @@ */ import * as path from 'path'; +import fs from 'fs'; import type { StorybookConfig } from '@storybook/core-common'; -import { Configuration } from 'webpack'; +import webpack, { Configuration } from 'webpack'; import webpackMerge from 'webpack-merge'; import { REPO_ROOT } from './constants'; import { default as WebpackConfig } from '../webpack.config'; +const MOCKS_DIRECTORY = '__storybook_mocks__'; +const EXTENSIONS = ['.ts', '.js']; + export type { StorybookConfig }; const toPath = (_path: string) => path.join(REPO_ROOT, _path); @@ -52,6 +56,48 @@ export const defaultConfig: StorybookConfig = { config.cache = true; } + // This will go over every component which is imported and check its import statements. + // For every import which starts with ./ it will do a check to see if a file with the same name + // exists in the __storybook_mocks__ folder. If it does, use that import instead. + // This allows you to mock hooks and functions when rendering components in Storybook. + // It is akin to Jest's manual mocks (__mocks__). + config.plugins?.push( + new webpack.NormalModuleReplacementPlugin(/^\.\//, async (resource: any) => { + if (!resource.contextInfo.issuer?.includes('node_modules')) { + const mockedPath = path.resolve(resource.context, MOCKS_DIRECTORY, resource.request); + + EXTENSIONS.forEach((ext) => { + const isReplacementPathExists = fs.existsSync(mockedPath + ext); + + if (isReplacementPathExists) { + const newImportPath = './' + path.join(MOCKS_DIRECTORY, resource.request); + resource.request = newImportPath; + } + }); + } + }) + ); + + // Same, but for imports statements which import modules outside of the directory (../) + config.plugins?.push( + new webpack.NormalModuleReplacementPlugin(/^\.\.\//, async (resource: any) => { + if (!resource.contextInfo.issuer?.includes('node_modules')) { + const prs = path.parse(resource.request); + + const mockedPath = path.resolve(resource.context, prs.dir, MOCKS_DIRECTORY, prs.base); + + EXTENSIONS.forEach((ext) => { + const isReplacementPathExists = fs.existsSync(mockedPath + ext); + + if (isReplacementPathExists) { + const newImportPath = prs.dir + '/' + path.join(MOCKS_DIRECTORY, prs.base); + resource.request = newImportPath; + } + }); + } + }) + ); + config.node = { fs: 'empty' }; config.watch = true; config.watchOptions = { diff --git a/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx b/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx index d35834e7cdd9d..da73befdf519e 100644 --- a/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx +++ b/packages/shared-ux/page/solution_nav/src/with_solution_nav.tsx @@ -8,8 +8,12 @@ import React, { ComponentType, ReactNode, useState } from 'react'; import classNames from 'classnames'; -import { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template-types'; -import { useIsWithinBreakpoints, useEuiTheme, useIsWithinMinBreakpoint } from '@elastic/eui'; +import { + useIsWithinBreakpoints, + useEuiTheme, + useIsWithinMinBreakpoint, + EuiPageSidebarProps, +} from '@elastic/eui'; import { SolutionNav, SolutionNavProps } from './solution_nav'; import { WithSolutionNavStyles } from './with_solution_nav.styles'; @@ -18,9 +22,11 @@ function getDisplayName(Component: ComponentType) { return Component.displayName || Component.name || 'UnnamedComponent'; } -type TemplateProps = Pick & { +export interface TemplateProps { children?: ReactNode; -}; + pageSideBar?: ReactNode; + pageSideBarProps?: EuiPageSidebarProps; +} type Props

= P & TemplateProps & { diff --git a/renovate.json b/renovate.json index d4a31a44a40b0..207ce758baaf8 100644 --- a/renovate.json +++ b/renovate.json @@ -143,6 +143,16 @@ "labels": ["Team:Operations", "release_note:skip"], "enabled": true }, + { + "groupName": "scss", + "packageNames": [ + "node-sass" + ], + "reviewers": ["team:kibana-operations"], + "matchBaseBranches": ["main"], + "labels": ["Team:Operations", "release_note:skip", "backport:all-open"], + "enabled": true + }, { "groupName": "@testing-library", "packageNames": [ @@ -164,7 +174,6 @@ "@jest/console", "@jest/reporters", "@jest/types", - "@types/jest", "babel-jest", "expect", "jest", diff --git a/scripts/run_scalability.js b/scripts/run_scalability.js new file mode 100644 index 0000000000000..1524beb7e9401 --- /dev/null +++ b/scripts/run_scalability.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +require('../src/setup_node_env'); +require('../src/dev/performance/run_scalability_cli'); diff --git a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts index 548dcbc6df01c..8870b81b70209 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts @@ -76,6 +76,7 @@ describe('checking migration metadata changes on all registered SO types', () => "cases-telemetry": "16e261e7378a72acd0806f18df92525dd1da4f37", "cases-user-actions": "3973dfcaacbe6ae147d7331699cfc25d2a27ca30", "config": "e3f0408976dbdd453641f5699927b28b188f6b8c", + "config-global": "b8f559884931609a349e129c717af73d23e7bc76", "connector_token": "fa5301aa5a2914795d3b1b82d0a49939444009da", "core-usage-stats": "f40a213da2c597b0de94e364a4326a5a1baa4ca9", "csp-rule-template": "3679c5f2431da8153878db79c78a4e695357fb61", diff --git a/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts index 188473320435d..ab4498a79941c 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts @@ -36,6 +36,7 @@ const previouslyRegisteredTypes = [ 'cases-user-actions', 'cases-telemetry', 'config', + 'config-global', 'connector_token', 'core-usage-stats', 'csp-rule-template', diff --git a/src/core/server/integration_tests/ui_settings/create_or_upgrade.test.ts b/src/core/server/integration_tests/ui_settings/create_or_upgrade.test.ts index c58821b467c78..2964d69010c8a 100644 --- a/src/core/server/integration_tests/ui_settings/create_or_upgrade.test.ts +++ b/src/core/server/integration_tests/ui_settings/create_or_upgrade.test.ts @@ -80,6 +80,7 @@ describe('createOrUpgradeSavedConfig()', () => { buildNum: 54099, log: logger, handleWriteErrors: false, + type: 'config', }); const config540 = await savedObjectsClient.get('config', '5.4.0'); @@ -108,6 +109,7 @@ describe('createOrUpgradeSavedConfig()', () => { buildNum: 54199, log: logger, handleWriteErrors: false, + type: 'config', }); const config541 = await savedObjectsClient.get('config', '5.4.1'); @@ -136,6 +138,7 @@ describe('createOrUpgradeSavedConfig()', () => { buildNum: 70010, log: logger, handleWriteErrors: false, + type: 'config', }); const config700rc1 = await savedObjectsClient.get('config', '7.0.0-rc1'); @@ -165,6 +168,7 @@ describe('createOrUpgradeSavedConfig()', () => { buildNum: 70099, log: logger, handleWriteErrors: false, + type: 'config', }); const config700 = await savedObjectsClient.get('config', '7.0.0'); @@ -195,6 +199,7 @@ describe('createOrUpgradeSavedConfig()', () => { buildNum: 62310, log: logger, handleWriteErrors: false, + type: 'config', }); const config623rc1 = await savedObjectsClient.get('config', '6.2.3-rc1'); diff --git a/src/core/server/integration_tests/ui_settings/doc_exists.ts b/src/core/server/integration_tests/ui_settings/doc_exists.ts index 8710be3e02c9e..60a0a5ca59c9f 100644 --- a/src/core/server/integration_tests/ui_settings/doc_exists.ts +++ b/src/core/server/integration_tests/ui_settings/doc_exists.ts @@ -12,7 +12,7 @@ export const docExistsSuite = (savedObjectsIndex: string) => () => { async function setup(options: { initialSettings?: Record } = {}) { const { initialSettings } = options; - const { uiSettings, esClient, supertest } = getServices(); + const { uiSettings, uiSettingsGlobal, esClient, supertest } = getServices(); // delete the kibana index to ensure we start fresh await esClient.deleteByQuery({ @@ -27,9 +27,10 @@ export const docExistsSuite = (savedObjectsIndex: string) => () => { if (initialSettings) { await uiSettings.setMany(initialSettings); + await uiSettingsGlobal.setMany(uiSettingsGlobal); } - return { uiSettings, supertest }; + return { uiSettings, uiSettingsGlobal, supertest }; } describe('get route', () => { @@ -190,4 +191,168 @@ export const docExistsSuite = (savedObjectsIndex: string) => () => { }); }); }); + + describe('global', () => { + describe('get route', () => { + it('returns a 200 and includes userValues', async () => { + const defaultIndex = chance.word({ length: 10 }); + + const { supertest } = await setup({ + initialSettings: { + defaultIndex, + }, + }); + + const { body } = await supertest('get', '/api/kibana/global_settings').expect(200); + + expect(body).toMatchObject({ + settings: { + buildNum: { + userValue: expect.any(Number), + }, + defaultIndex: { + userValue: defaultIndex, + }, + foo: { + userValue: 'bar', + isOverridden: true, + }, + }, + }); + }); + }); + + describe('set route', () => { + it('returns a 200 and all values including update', async () => { + const { supertest } = await setup(); + + const defaultIndex = chance.word(); + + const { body } = await supertest('post', '/api/kibana/global_settings/defaultIndex') + .send({ + value: defaultIndex, + }) + .expect(200); + + expect(body).toMatchObject({ + settings: { + buildNum: { + userValue: expect.any(Number), + }, + defaultIndex: { + userValue: defaultIndex, + }, + foo: { + userValue: 'bar', + isOverridden: true, + }, + }, + }); + }); + + it('returns a 400 if trying to set overridden value', async () => { + const { supertest } = await setup(); + + const { body } = await supertest('delete', '/api/kibana/global_settings/foo') + .send({ + value: 'baz', + }) + .expect(400); + + expect(body).toEqual({ + error: 'Bad Request', + message: 'Unable to update "foo" because it is overridden', + statusCode: 400, + }); + }); + }); + + describe('setMany route', () => { + it('returns a 200 and all values including updates', async () => { + const { supertest } = await setup(); + + const defaultIndex = chance.word(); + const { body } = await supertest('post', '/api/kibana/global_settings') + .send({ + changes: { + defaultIndex, + }, + }) + .expect(200); + + expect(body).toMatchObject({ + settings: { + buildNum: { + userValue: expect.any(Number), + }, + defaultIndex: { + userValue: defaultIndex, + }, + foo: { + userValue: 'bar', + isOverridden: true, + }, + }, + }); + }); + + it('returns a 400 if trying to set overridden value', async () => { + const { supertest } = await setup(); + + const { body } = await supertest('post', '/api/kibana/global_settings') + .send({ + changes: { + foo: 'baz', + }, + }) + .expect(400); + + expect(body).toEqual({ + error: 'Bad Request', + message: 'Unable to update "foo" because it is overridden', + statusCode: 400, + }); + }); + }); + + describe('delete route', () => { + it('returns a 200 and deletes the setting', async () => { + const defaultIndex = chance.word({ length: 10 }); + + const { uiSettingsGlobal, supertest } = await setup({ + initialSettings: { defaultIndex }, + }); + + expect(await uiSettingsGlobal.get('defaultIndex')).toBe(defaultIndex); + + const { body } = await supertest( + 'delete', + '/api/kibana/global_settings/defaultIndex' + ).expect(200); + + expect(body).toMatchObject({ + settings: { + buildNum: { + userValue: expect.any(Number), + }, + foo: { + userValue: 'bar', + isOverridden: true, + }, + }, + }); + }); + it('returns a 400 if deleting overridden value', async () => { + const { supertest } = await setup(); + + const { body } = await supertest('delete', '/api/kibana/global_settings/foo').expect(400); + + expect(body).toEqual({ + error: 'Bad Request', + message: 'Unable to update "foo" because it is overridden', + statusCode: 400, + }); + }); + }); + }); }; diff --git a/src/core/server/integration_tests/ui_settings/lib/servers.ts b/src/core/server/integration_tests/ui_settings/lib/servers.ts index 64d3fa170a796..a02b2fb86b119 100644 --- a/src/core/server/integration_tests/ui_settings/lib/servers.ts +++ b/src/core/server/integration_tests/ui_settings/lib/servers.ts @@ -28,6 +28,7 @@ interface AllServices { savedObjectsClient: SavedObjectsClientContract; esClient: Client; uiSettings: IUiSettingsClient; + uiSettingsGlobal: IUiSettingsClient; supertest: (method: HttpMethod, path: string) => supertest.Test; } @@ -62,12 +63,14 @@ export function getServices() { ); const uiSettings = kbn.coreStart.uiSettings.asScopedToClient(savedObjectsClient); + const uiSettingsGlobal = kbn.coreStart.uiSettings.globalAsScopedToClient(savedObjectsClient); services = { supertest: (method: HttpMethod, path: string) => getSupertest(kbn.root, method, path), esClient, savedObjectsClient, uiSettings, + uiSettingsGlobal, }; return services; diff --git a/src/core/server/integration_tests/ui_settings/routes.test.ts b/src/core/server/integration_tests/ui_settings/routes.test.ts index 50adc8003aca3..ba806d04079ee 100644 --- a/src/core/server/integration_tests/ui_settings/routes.test.ts +++ b/src/core/server/integration_tests/ui_settings/routes.test.ts @@ -55,5 +55,32 @@ describe('ui settings service', () => { ); }); }); + + describe('global', () => { + describe('set', () => { + it('validates value', async () => { + const response = await request + .post(root, '/api/kibana/global_settings/custom') + .send({ value: 100 }) + .expect(400); + + expect(response.body.message).toBe( + '[validation [custom]]: expected value of type [string] but got [number]' + ); + }); + }); + describe('set many', () => { + it('validates value', async () => { + const response = await request + .post(root, '/api/kibana/global_settings') + .send({ changes: { custom: 100, foo: 'bar' } }) + .expect(400); + + expect(response.body.message).toBe( + '[validation [custom]]: expected value of type [string] but got [number]' + ); + }); + }); + }); }); }); diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 028465ebfb8ac..b2e1deb4e3c07 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -124,6 +124,7 @@ function createCoreRequestHandlerContextMock() { }, uiSettings: { client: uiSettingsServiceMock.createClient(), + globalClient: uiSettingsServiceMock.createClient(), }, deprecations: { client: deprecationsServiceMock.createClient(), diff --git a/src/dev/build/tasks/bundle_fleet_packages.ts b/src/dev/build/tasks/bundle_fleet_packages.ts index f7fe9ffc76573..3bbef9acfc39d 100644 --- a/src/dev/build/tasks/bundle_fleet_packages.ts +++ b/src/dev/build/tasks/bundle_fleet_packages.ts @@ -10,14 +10,12 @@ import JSON5 from 'json5'; import fs from 'fs/promises'; import { safeLoad, safeDump } from 'js-yaml'; -import { readCliArgs } from '../args'; import { Task, read, downloadToDisk, unzipBuffer, createZipFile } from '../lib'; const BUNDLED_PACKAGES_DIR = 'x-pack/plugins/fleet/target/bundled_packages'; -// APM needs to directly request its versions from Package Storage v2 - this should -// be removed when Package Storage v2 is in production -const PACKAGE_STORAGE_V2_URL = 'https://epr-v2.ea-web.elastic.dev'; +// Package storage v2 url +export const PACKAGE_STORAGE_REGISTRY_URL = 'https://epr.elastic.co'; interface FleetPackage { name: string; @@ -32,13 +30,6 @@ export const BundleFleetPackages: Task = { log.info('Fetching fleet packages from package registry'); log.indent(4); - // Support the `--epr-registry` command line argument to fetch from the snapshot or production registry - const { buildOptions } = readCliArgs(process.argv); - const eprUrl = - buildOptions?.eprRegistry === 'snapshot' - ? 'https://epr-snapshot.elastic.co' - : 'https://epr.elastic.co'; - const configFilePath = config.resolveFromRepo('fleet_packages.json'); const fleetPackages = (await read(configFilePath)) || '[]'; @@ -68,16 +59,7 @@ export const BundleFleetPackages: Task = { } const archivePath = `${fleetPackage.name}-${versionToWrite}.zip`; - let archiveUrl = `${eprUrl}/epr/${fleetPackage.name}/${fleetPackage.name}-${fleetPackage.version}.zip`; - - // Point APM, Endpoint and Synthetics packages to package storage v2 - if ( - fleetPackage.name === 'apm' || - fleetPackage.name === 'endpoint' || - fleetPackage.name === 'synthetics' - ) { - archiveUrl = `${PACKAGE_STORAGE_V2_URL}/epr/${fleetPackage.name}/${fleetPackage.name}-${fleetPackage.version}.zip`; - } + const archiveUrl = `${PACKAGE_STORAGE_REGISTRY_URL}/epr/${fleetPackage.name}/${fleetPackage.name}-${fleetPackage.version}.zip`; const destination = build.resolvePath(BUNDLED_PACKAGES_DIR, archivePath); diff --git a/src/dev/performance/run_scalability_cli.ts b/src/dev/performance/run_scalability_cli.ts new file mode 100644 index 0000000000000..eec11e611f3e1 --- /dev/null +++ b/src/dev/performance/run_scalability_cli.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createFlagError } from '@kbn/dev-cli-errors'; +import { run } from '@kbn/dev-cli-runner'; +import { REPO_ROOT } from '@kbn/utils'; +import fs from 'fs'; +import path from 'path'; + +run( + async ({ log, flagsReader, procRunner }) => { + const kibanaInstallDir = flagsReader.path('kibana-install-dir'); + const journeyConfigPath = flagsReader.requiredPath('journey-config-path'); + + if (kibanaInstallDir && !fs.existsSync(kibanaInstallDir)) { + throw createFlagError('--kibana-install-dir must be an existing directory'); + } + if ( + !fs.existsSync(journeyConfigPath) || + (!fs.statSync(journeyConfigPath).isDirectory() && path.extname(journeyConfigPath) !== '.json') + ) { + throw createFlagError( + '--journey-config-path must be an existing directory or scalability json path' + ); + } + + const journeys = fs.statSync(journeyConfigPath).isDirectory() + ? fs + .readdirSync(journeyConfigPath) + .filter((fileName) => path.extname(fileName) === '.json') + .map((fileName) => path.resolve(journeyConfigPath, fileName)) + : [journeyConfigPath]; + + log.info(`Found ${journeys.length} journeys to run:\n${JSON.stringify(journeys)}`); + + const failedJourneys = []; + + for (const journey of journeys) { + try { + process.stdout.write(`--- Running scalability journey: ${journey}\n`); + await runScalabilityJourney(journey, kibanaInstallDir); + } catch (e) { + log.error(e); + failedJourneys.push(journey); + } + } + + if (failedJourneys.length > 0) { + throw new Error(`${failedJourneys.length} journeys failed: ${failedJourneys.join(',')}`); + } + + async function runScalabilityJourney(filePath: string, kibanaDir?: string) { + // Pass in a clean APM environment, so that FTR can later + // set it's own values. + const cleanApmEnv = { + ELASTIC_APM_ACTIVE: undefined, + ELASTIC_APM_BREAKDOWN_METRICS: undefined, + ELASTIC_APM_CONTEXT_PROPAGATION_ONLY: undefined, + ELASTIC_APM_CAPTURE_SPAN_STACK_TRACES: undefined, + ELASTIC_APM_ENVIRONMENT: undefined, + ELASTIC_APM_GLOBAL_LABELS: undefined, + ELASTIC_APM_MAX_QUEUE_SIZE: undefined, + ELASTIC_APM_METRICS_INTERVAL: undefined, + ELASTIC_APM_SERVER_URL: undefined, + ELASTIC_APM_SECRET_TOKEN: undefined, + ELASTIC_APM_TRANSACTION_SAMPLE_RATE: undefined, + }; + + await procRunner.run('scalability-tests', { + cmd: 'node', + args: [ + 'scripts/functional_tests', + ['--config', 'x-pack/test/scalability/config.ts'], + kibanaDir ? ['--kibana-install-dir', kibanaDir] : [], + '--debug', + '--logToFile', + '--bail', + ].flat(), + cwd: REPO_ROOT, + wait: true, + env: { + ...cleanApmEnv, + SCALABILITY_JOURNEY_PATH: filePath, // journey json file for Gatling test runner + KIBANA_DIR: REPO_ROOT, // Gatling test runner use it to find kbn/es archives + }, + }); + } + }, + { + flags: { + string: ['kibana-install-dir', 'journey-config-path'], + help: ` + --kibana-install-dir Run Kibana from existing install directory instead of from source + --journey-config-path Define a scalability journey config or directory with multiple + configs that should be executed + `, + }, + } +); diff --git a/src/plugins/controls/public/control_group/control_group_input_builder.ts b/src/plugins/controls/public/control_group/control_group_input_builder.ts index 867eea1c65f9c..a128025d52548 100644 --- a/src/plugins/controls/public/control_group/control_group_input_builder.ts +++ b/src/plugins/controls/public/control_group/control_group_input_builder.ts @@ -6,8 +6,9 @@ * Side Public License, v 1. */ +import { i18n } from '@kbn/i18n'; import uuid from 'uuid'; -import { ControlPanelState } from '../../common'; +import { ControlPanelState, OptionsListEmbeddableInput } from '../../common'; import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH, @@ -33,9 +34,7 @@ export interface AddDataControlProps { width?: ControlWidth; } -export type AddOptionsListControlProps = AddDataControlProps & { - selectedOptions?: string[]; -}; +export type AddOptionsListControlProps = AddDataControlProps & Partial; export type AddRangeSliderControlProps = AddDataControlProps & { value?: RangeValue; @@ -46,104 +45,113 @@ export const controlGroupInputBuilder = { initialInput: Partial, controlProps: AddDataControlProps ) => { - const { controlId, dataViewId, fieldName, title } = controlProps; - const panelId = controlId ? controlId : uuid.v4(); + const panelState = await getDataControlPanelState(initialInput, controlProps); initialInput.panels = { ...initialInput.panels, - [panelId]: { - order: getNextPanelOrder(initialInput), - type: await getCompatibleControlType({ dataViewId, fieldName }), - grow: getGrow(initialInput, controlProps), - width: getWidth(initialInput, controlProps), - explicitInput: { - id: panelId, - dataViewId, - fieldName, - title: title ?? fieldName, - }, - } as ControlPanelState, + [panelState.explicitInput.id]: panelState, }; }, addOptionsListControl: ( initialInput: Partial, controlProps: AddOptionsListControlProps ) => { - const { controlId, dataViewId, fieldName, selectedOptions, title } = controlProps; - const panelId = controlId ? controlId : uuid.v4(); + const panelState = getOptionsListPanelState(initialInput, controlProps); initialInput.panels = { ...initialInput.panels, - [panelId]: { - order: getNextPanelOrder(initialInput), - type: OPTIONS_LIST_CONTROL, - grow: getGrow(initialInput, controlProps), - width: getWidth(initialInput, controlProps), - explicitInput: { - id: panelId, - dataViewId, - fieldName, - selectedOptions, - title: title ?? fieldName, - }, - } as ControlPanelState, + [panelState.explicitInput.id]: panelState, }; }, addRangeSliderControl: ( initialInput: Partial, controlProps: AddRangeSliderControlProps ) => { - const { controlId, dataViewId, fieldName, title, value } = controlProps; - const panelId = controlId ? controlId : uuid.v4(); + const panelState = getRangeSliderPanelState(initialInput, controlProps); initialInput.panels = { ...initialInput.panels, - [panelId]: { - order: getNextPanelOrder(initialInput), - type: RANGE_SLIDER_CONTROL, - grow: getGrow(initialInput, controlProps), - width: getWidth(initialInput, controlProps), - explicitInput: { - id: panelId, - dataViewId, - fieldName, - title: title ?? fieldName, - value: value ? value : ['', ''], - }, - } as ControlPanelState, + [panelState.explicitInput.id]: panelState, }; }, addTimeSliderControl: (initialInput: Partial) => { - const panelId = uuid.v4(); + const panelState = getTimeSliderPanelState(initialInput); initialInput.panels = { ...initialInput.panels, - [panelId]: { - order: getNextPanelOrder(initialInput), - type: TIME_SLIDER_CONTROL, - grow: true, - width: 'large', - explicitInput: { - id: panelId, - title: 'timeslider', - }, - } as ControlPanelState, + [panelState.explicitInput.id]: panelState, }; }, }; -function getGrow(initialInput: Partial, controlProps: AddDataControlProps) { - if (typeof controlProps.grow === 'boolean') { - return controlProps.grow; - } +export async function getDataControlPanelState( + input: Partial, + controlProps: AddDataControlProps +) { + const { controlId, dataViewId, fieldName, title } = controlProps; + return { + type: await getCompatibleControlType({ dataViewId, fieldName }), + ...getPanelState(input, controlProps), + explicitInput: { + id: controlId ? controlId : uuid.v4(), + dataViewId, + fieldName, + title: title ?? fieldName, + }, + } as ControlPanelState; +} - return typeof initialInput.defaultControlGrow === 'boolean' - ? initialInput.defaultControlGrow - : DEFAULT_CONTROL_GROW; +export function getOptionsListPanelState( + input: Partial, + controlProps: AddOptionsListControlProps +) { + const { controlId, dataViewId, fieldName, title, ...rest } = controlProps; + return { + type: OPTIONS_LIST_CONTROL, + ...getPanelState(input, controlProps), + explicitInput: { + id: controlId ? controlId : uuid.v4(), + dataViewId, + fieldName, + title: title ?? fieldName, + ...rest, + }, + } as ControlPanelState; } -function getWidth(initialInput: Partial, controlProps: AddDataControlProps) { - if (controlProps.width) { - return controlProps.width; - } +export function getRangeSliderPanelState( + input: Partial, + controlProps: AddRangeSliderControlProps +) { + const { controlId, dataViewId, fieldName, title, ...rest } = controlProps; + return { + type: RANGE_SLIDER_CONTROL, + ...getPanelState(input, controlProps), + explicitInput: { + id: controlId ? controlId : uuid.v4(), + dataViewId, + fieldName, + title: title ?? fieldName, + ...rest, + }, + } as ControlPanelState; +} + +export function getTimeSliderPanelState(input: Partial) { + return { + type: TIME_SLIDER_CONTROL, + order: getNextPanelOrder(input.panels), + grow: true, + width: 'large', + explicitInput: { + id: uuid.v4(), + title: i18n.translate('controls.controlGroup.timeSlider.title', { + defaultMessage: 'Time slider', + }), + }, + } as ControlPanelState; +} - return initialInput.defaultControlWidth - ? initialInput.defaultControlWidth - : DEFAULT_CONTROL_WIDTH; +function getPanelState(input: Partial, controlProps: AddDataControlProps) { + return { + order: getNextPanelOrder(input.panels), + grow: controlProps.grow ?? input.defaultControlGrow ?? DEFAULT_CONTROL_GROW, + width: controlProps.width ?? input.defaultControlWidth ?? DEFAULT_CONTROL_WIDTH, + }; } diff --git a/src/plugins/controls/public/control_group/editor/create_time_slider_control.tsx b/src/plugins/controls/public/control_group/editor/create_time_slider_control.tsx index 9137bab8b8068..3688422a2eebb 100644 --- a/src/plugins/controls/public/control_group/editor/create_time_slider_control.tsx +++ b/src/plugins/controls/public/control_group/editor/create_time_slider_control.tsx @@ -9,17 +9,15 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; -import type { ControlInput } from '../../types'; -import { TIME_SLIDER_CONTROL } from '../../time_slider/types'; interface Props { - addNewEmbeddable: (type: string, input: Omit) => void; + onCreate: () => void; closePopover?: () => void; hasTimeSliderControl: boolean; } export const CreateTimeSliderControlButton = ({ - addNewEmbeddable, + onCreate, closePopover, hasTimeSliderControl, }: Props) => { @@ -27,11 +25,7 @@ export const CreateTimeSliderControlButton = ({ { - addNewEmbeddable(TIME_SLIDER_CONTROL, { - title: i18n.translate('controls.controlGroup.timeSlider.title', { - defaultMessage: 'Time slider', - }), - }); + onCreate(); if (closePopover) { closePopover(); } 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 3e95100d95cfe..46774a0747b89 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 @@ -40,11 +40,22 @@ import { ControlGroupStrings } from '../control_group_strings'; import { EditControlGroup } from '../editor/edit_control_group'; import { ControlGroup } from '../component/control_group_component'; import { controlGroupReducers } from '../state/control_group_reducers'; -import { ControlEmbeddable, ControlInput, ControlOutput, DataControlInput } from '../../types'; +import { OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL, TIME_SLIDER_CONTROL } from '../..'; +import { ControlEmbeddable, ControlInput, ControlOutput } from '../../types'; import { CreateControlButton, CreateControlButtonTypes } from '../editor/create_control'; import { CreateTimeSliderControlButton } from '../editor/create_time_slider_control'; -import { TIME_SLIDER_CONTROL } from '../../time_slider'; -import { getCompatibleControlType, getNextPanelOrder } from './control_group_helpers'; +import { getNextPanelOrder } from './control_group_helpers'; +import type { + AddDataControlProps, + AddOptionsListControlProps, + AddRangeSliderControlProps, +} from '../control_group_input_builder'; +import { + getDataControlPanelState, + getOptionsListPanelState, + getRangeSliderPanelState, + getTimeSliderPanelState, +} from '../control_group_input_builder'; let flyoutRef: OverlayRef | undefined; export const setFlyoutRef = (newRef: OverlayRef | undefined) => { @@ -96,23 +107,24 @@ export class ControlGroupContainer extends Container< flyoutRef = undefined; } - public async addDataControlFromField({ - uuid, - dataViewId, - fieldName, - title, - }: { - uuid?: string; - dataViewId: string; - fieldName: string; - title?: string; - }) { - return this.addNewEmbeddable(await getCompatibleControlType({ dataViewId, fieldName }), { - id: uuid, - dataViewId, - fieldName, - title: title ?? fieldName, - } as DataControlInput); + public async addDataControlFromField(controlProps: AddDataControlProps) { + const panelState = await getDataControlPanelState(this.getInput(), controlProps); + return this.createAndSaveEmbeddable(panelState.type, panelState); + } + + public addOptionsListControl(controlProps: AddOptionsListControlProps) { + const panelState = getOptionsListPanelState(this.getInput(), controlProps); + return this.createAndSaveEmbeddable(panelState.type, panelState); + } + + public addRangeSliderControl(controlProps: AddRangeSliderControlProps) { + const panelState = getRangeSliderPanelState(this.getInput(), controlProps); + return this.createAndSaveEmbeddable(panelState.type, panelState); + } + + public addTimeSliderControl() { + const panelState = getTimeSliderPanelState(this.getInput()); + return this.createAndSaveEmbeddable(panelState.type, panelState); } /** @@ -138,7 +150,19 @@ export class ControlGroupContainer extends Container< updateDefaultGrow={(defaultControlGrow: boolean) => this.updateInput({ defaultControlGrow }) } - addNewEmbeddable={(type, input) => this.addNewEmbeddable(type, input)} + addNewEmbeddable={(type, input) => { + if (type === OPTIONS_LIST_CONTROL) { + this.addOptionsListControl(input as AddOptionsListControlProps); + return; + } + + if (type === RANGE_SLIDER_CONTROL) { + this.addRangeSliderControl(input as AddRangeSliderControlProps); + return; + } + + this.addDataControlFromField(input as AddDataControlProps); + }} closePopover={closePopover} getRelevantDataViewId={() => this.getMostRelevantDataViewId()} setLastUsedDataViewId={(newId) => this.setLastUsedDataViewId(newId)} @@ -155,7 +179,9 @@ export class ControlGroupContainer extends Container< }); return ( this.addNewEmbeddable(type, input)} + onCreate={() => { + this.addTimeSliderControl(); + }} closePopover={closePopover} hasTimeSliderControl={hasTimeSliderControl} /> @@ -317,12 +343,10 @@ export class ControlGroupContainer extends Container< partial: Partial = {} ): ControlPanelState { const panelState = super.createNewPanelState(factory, partial); - const nextOrder = getNextPanelOrder(this.getInput()); return { - order: nextOrder, - width: - panelState.type === TIME_SLIDER_CONTROL ? 'large' : this.getInput().defaultControlWidth, - grow: panelState.type === TIME_SLIDER_CONTROL ? true : this.getInput().defaultControlGrow, + order: getNextPanelOrder(this.getInput().panels), + width: this.getInput().defaultControlWidth, + grow: this.getInput().defaultControlGrow, ...panelState, } as ControlPanelState; } diff --git a/src/plugins/controls/public/control_group/embeddable/control_group_helpers.ts b/src/plugins/controls/public/control_group/embeddable/control_group_helpers.ts index 817cf9c280155..1afcdc539bf87 100644 --- a/src/plugins/controls/public/control_group/embeddable/control_group_helpers.ts +++ b/src/plugins/controls/public/control_group/embeddable/control_group_helpers.ts @@ -6,15 +6,15 @@ * Side Public License, v 1. */ -import { ControlGroupInput } from '../types'; +import { ControlsPanels } from '../types'; import { pluginServices } from '../../services'; import { getDataControlFieldRegistry } from '../editor/data_control_editor_tools'; -export const getNextPanelOrder = (initialInput: Partial) => { +export const getNextPanelOrder = (panels?: ControlsPanels) => { let nextOrder = 0; - if (Object.keys(initialInput.panels ?? {}).length > 0) { + if (Object.keys(panels ?? {}).length > 0) { nextOrder = - Object.values(initialInput.panels ?? {}).reduce((highestSoFar, panel) => { + Object.values(panels ?? {}).reduce((highestSoFar, panel) => { if (panel.order > highestSoFar) highestSoFar = panel.order; return highestSoFar; }, 0) + 1; diff --git a/src/plugins/controls/public/time_slider/embeddable/time_slider_embeddable.tsx b/src/plugins/controls/public/time_slider/embeddable/time_slider_embeddable.tsx index 05a8a17a22908..e12320356dd66 100644 --- a/src/plugins/controls/public/time_slider/embeddable/time_slider_embeddable.tsx +++ b/src/plugins/controls/public/time_slider/embeddable/time_slider_embeddable.tsx @@ -49,6 +49,7 @@ export class TimeSliderControlEmbeddable extends Embeddable< private getTimezone: ControlsSettingsService['getTimezone']; private timefilter: ControlsDataService['timefilter']; + private prevTimeRange: TimeRange | undefined; private readonly waitForControlOutputConsumersToLoad$; private reduxEmbeddableTools: ReduxEmbeddableTools< @@ -129,9 +130,9 @@ export class TimeSliderControlEmbeddable extends Embeddable< return; } - const nextBounds = this.timeRangeToBounds(input.timeRange); - const { actions, dispatch, getState } = this.reduxEmbeddableTools; - if (!_.isEqual(nextBounds, getState().componentState.timeRangeBounds)) { + if (!_.isEqual(input.timeRange, this.prevTimeRange)) { + const { actions, dispatch } = this.reduxEmbeddableTools; + const nextBounds = this.timeRangeToBounds(input.timeRange); const ticks = getTicks(nextBounds[FROM_INDEX], nextBounds[TO_INDEX], this.getTimezone()); dispatch( actions.setTimeRangeBounds({ @@ -145,6 +146,7 @@ export class TimeSliderControlEmbeddable extends Embeddable< } private syncWithTimeRange() { + this.prevTimeRange = this.getInput().timeRange; const { actions, dispatch, getState } = this.reduxEmbeddableTools; const stepSize = getState().componentState.stepSize; const timesliceStartAsPercentageOfTimeRange = diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_action.test.tsx similarity index 74% rename from src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx rename to src/plugins/dashboard/public/application/actions/filters_notification_action.test.tsx index 3b3fb5dde0497..be9dc25f69fb9 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.test.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_action.test.tsx @@ -6,12 +6,7 @@ * Side Public License, v 1. */ -import { - IContainer, - ErrorEmbeddable, - isErrorEmbeddable, - FilterableEmbeddable, -} from '@kbn/embeddable-plugin/public'; +import { ErrorEmbeddable, isErrorEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { ContactCardEmbeddable, CONTACT_CARD_EMBEDDABLE, @@ -25,16 +20,13 @@ import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { getSampleDashboardInput } from '../test_helpers'; import { pluginServices } from '../../services/plugin_services'; import { DashboardContainer } from '../embeddable/dashboard_container'; -import { FiltersNotificationBadge } from './filters_notification_badge'; +import { FiltersNotificationAction } from './filters_notification_action'; const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); pluginServices.getServices().embeddable.getEmbeddableFactory = jest .fn() .mockReturnValue(mockEmbeddableFactory); -let action: FiltersNotificationBadge; -let container: DashboardContainer; -let embeddable: ContactCardEmbeddable & FilterableEmbeddable; const mockGetFilters = jest.fn(async () => [] as Filter[]); const mockGetQuery = jest.fn(async () => undefined as Query | AggregateQuery | undefined); @@ -58,46 +50,54 @@ const getMockPhraseFilter = (key: string, value: string) => { }; }; -beforeEach(async () => { - container = new DashboardContainer(getSampleDashboardInput()); - +const buildEmbeddable = async (input?: Partial) => { + const container = new DashboardContainer(getSampleDashboardInput()); const contactCardEmbeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, ContactCardEmbeddableOutput, ContactCardEmbeddable >(CONTACT_CARD_EMBEDDABLE, { firstName: 'Kibanana', + viewMode: ViewMode.EDIT, + ...input, }); if (isErrorEmbeddable(contactCardEmbeddable)) { throw new Error('Failed to create embeddable'); } - action = new FiltersNotificationBadge(); - embeddable = embeddablePluginMock.mockFilterableEmbeddable(contactCardEmbeddable, { + const embeddable = embeddablePluginMock.mockFilterableEmbeddable(contactCardEmbeddable, { getFilters: () => mockGetFilters(), getQuery: () => mockGetQuery(), }); -}); + + return embeddable; +}; + +const action = new FiltersNotificationAction(); test('Badge is incompatible with Error Embeddables', async () => { - const errorEmbeddable = new ErrorEmbeddable( - 'Wow what an awful error', - { id: ' 404' }, - embeddable.getRoot() as IContainer - ); + const errorEmbeddable = new ErrorEmbeddable('Wow what an awful error', { id: ' 404' }); expect(await action.isCompatible({ embeddable: errorEmbeddable })).toBe(false); }); test('Badge is not shown when panel has no app-level filters or queries', async () => { + const embeddable = await buildEmbeddable(); expect(await action.isCompatible({ embeddable })).toBe(false); }); test('Badge is shown when panel has at least one app-level filter', async () => { + const embeddable = await buildEmbeddable(); mockGetFilters.mockResolvedValue([getMockPhraseFilter('fieldName', 'someValue')] as Filter[]); expect(await action.isCompatible({ embeddable })).toBe(true); }); test('Badge is shown when panel has at least one app-level query', async () => { + const embeddable = await buildEmbeddable(); mockGetQuery.mockResolvedValue({ sql: 'SELECT * FROM test_dataview' } as AggregateQuery); expect(await action.isCompatible({ embeddable })).toBe(true); }); + +test('Badge is not shown in view mode', async () => { + const embeddable = await buildEmbeddable({ viewMode: ViewMode.VIEW }); + expect(await action.isCompatible({ embeddable })).toBe(false); +}); diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_action.tsx similarity index 64% rename from src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx rename to src/plugins/dashboard/public/application/actions/filters_notification_action.tsx index 6dbe7d5dbe3c9..b7ee2311ebd18 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_badge.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_action.tsx @@ -8,15 +8,17 @@ import React from 'react'; -import { EditPanelAction, isFilterableEmbeddable } from '@kbn/embeddable-plugin/public'; -import { type AggregateQuery } from '@kbn/es-query'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import type { ApplicationStart } from '@kbn/core/public'; -import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; -import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { EditPanelAction, isFilterableEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { type IEmbeddable, isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; +import { KibanaThemeProvider, reactToUiComponent } from '@kbn/kibana-react-plugin/public'; +import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; +import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; +import type { ApplicationStart } from '@kbn/core/public'; +import { type AggregateQuery } from '@kbn/es-query'; +import { I18nProvider } from '@kbn/i18n-react'; -import { dashboardFilterNotificationBadge } from '../../dashboard_strings'; +import { FiltersNotificationPopover } from './filters_notification_popover'; +import { dashboardFilterNotificationAction } from '../../dashboard_strings'; import { pluginServices } from '../../services/plugin_services'; export const BADGE_FILTERS_NOTIFICATION = 'ACTION_FILTERS_NOTIFICATION'; @@ -25,27 +27,57 @@ export interface FiltersNotificationActionContext { embeddable: IEmbeddable; } -export class FiltersNotificationBadge implements Action { +export class FiltersNotificationAction implements Action { public readonly id = BADGE_FILTERS_NOTIFICATION; public readonly type = BADGE_FILTERS_NOTIFICATION; public readonly order = 2; - private displayName = dashboardFilterNotificationBadge.getDisplayName(); + private displayName = dashboardFilterNotificationAction.getDisplayName(); private icon = 'filter'; private applicationService; private embeddableService; private settingsService; - private openModal; constructor() { ({ application: this.applicationService, embeddable: this.embeddableService, - overlays: { openModal: this.openModal }, settings: this.settingsService, } = pluginServices.getServices()); } + private FilterIconButton = ({ context }: { context: FiltersNotificationActionContext }) => { + const { embeddable } = context; + + const editPanelAction = new EditPanelAction( + this.embeddableService.getEmbeddableFactory, + this.applicationService as unknown as ApplicationStart, + this.embeddableService.getStateTransfer() + ); + + const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ + uiSettings: this.settingsService.uiSettings, + }); + + return ( + + + + + + + + ); + }; + + public readonly MenuItem = reactToUiComponent(this.FilterIconButton); + public getDisplayName({ embeddable }: FiltersNotificationActionContext) { if (!embeddable.getRoot() || !embeddable.getRoot().isContainer) { throw new IncompatibleActionError(); @@ -65,6 +97,7 @@ export class FiltersNotificationBadge implements Action { - const { embeddable } = context; - - const isCompatible = await this.isCompatible({ embeddable }); - if (!isCompatible || !isFilterableEmbeddable(embeddable)) { - throw new IncompatibleActionError(); - } - - const { - uiSettings, - theme: { theme$ }, - } = this.settingsService; - const { getEmbeddableFactory, getStateTransfer } = this.embeddableService; - - const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ - uiSettings, - }); - const editPanelAction = new EditPanelAction( - getEmbeddableFactory, - this.applicationService as unknown as ApplicationStart, - getStateTransfer() - ); - const FiltersNotificationModal = await import('./filters_notification_modal').then( - (m) => m.FiltersNotificationModal - ); - - const session = this.openModal( - toMountPoint( - - session.close()} - /> - , - { theme$ } - ), - { - 'data-test-subj': 'filtersNotificationModal', - } - ); - }; + public execute = async () => {}; } diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx deleted file mode 100644 index b188674a85b69..0000000000000 --- a/src/plugins/dashboard/public/application/actions/filters_notification_modal.tsx +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useState } from 'react'; -import useMount from 'react-use/lib/useMount'; - -import { - EuiButton, - EuiButtonEmpty, - EuiCodeBlock, - EuiFlexGroup, - EuiFlexItem, - EuiForm, - EuiFormRow, - EuiLoadingContent, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, -} from '@elastic/eui'; -import { css } from '@emotion/react'; -import { DataView } from '@kbn/data-views-plugin/public'; -import { - EditPanelAction, - FilterableEmbeddable, - IEmbeddable, - ViewMode, -} from '@kbn/embeddable-plugin/public'; -import { - type AggregateQuery, - type Filter, - getAggregateQueryMode, - isOfQueryType, -} from '@kbn/es-query'; -import { FilterItems } from '@kbn/unified-search-plugin/public'; - -import { FiltersNotificationActionContext } from './filters_notification_badge'; -import { DashboardContainer } from '../embeddable'; -import { dashboardFilterNotificationBadge } from '../../dashboard_strings'; - -export interface FiltersNotificationProps { - context: FiltersNotificationActionContext; - displayName: string; - id: string; - editPanelAction: EditPanelAction; - onClose: () => void; -} - -export function FiltersNotificationModal({ - context, - displayName, - id, - editPanelAction, - onClose, -}: FiltersNotificationProps) { - const { embeddable } = context; - const [isLoading, setIsLoading] = useState(true); - const [filters, setFilters] = useState([]); - const [queryString, setQueryString] = useState(''); - const [queryLanguage, setQueryLanguage] = useState<'sql' | 'esql' | undefined>(); - - useMount(() => { - Promise.all([ - (embeddable as IEmbeddable & FilterableEmbeddable).getFilters(), - (embeddable as IEmbeddable & FilterableEmbeddable).getQuery(), - ]).then(([embeddableFilters, embeddableQuery]) => { - setFilters(embeddableFilters); - if (embeddableQuery) { - if (isOfQueryType(embeddableQuery)) { - setQueryString(embeddableQuery.query as string); - } else { - const language = getAggregateQueryMode(embeddableQuery); - setQueryLanguage(language); - setQueryString(embeddableQuery[language as keyof AggregateQuery]); - } - } - setIsLoading(false); - }); - }); - - const dataViewList: DataView[] = (embeddable.getRoot() as DashboardContainer)?.getAllDataViews(); - const viewMode = embeddable.getInput().viewMode; - - return ( - <> - - -

{displayName}

- - - - - {isLoading ? ( - - ) : ( - - {queryString !== '' && ( - - - {queryString} - - - )} - {filters && filters.length > 0 && ( - - - - - - )} - - )} - - - {viewMode !== ViewMode.VIEW && ( - - - - - {dashboardFilterNotificationBadge.getCloseButtonTitle()} - - - - { - onClose(); - editPanelAction.execute(context); - }} - fill - > - {dashboardFilterNotificationBadge.getEditButtonTitle()} - - - - - )} - - ); -} diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_modal.test.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_popover.test.tsx similarity index 63% rename from src/plugins/dashboard/public/application/actions/filters_notification_modal.test.tsx rename to src/plugins/dashboard/public/application/actions/filters_notification_popover.test.tsx index 9a83bf52e1092..92608264125ef 100644 --- a/src/plugins/dashboard/public/application/actions/filters_notification_modal.test.tsx +++ b/src/plugins/dashboard/public/application/actions/filters_notification_popover.test.tsx @@ -7,14 +7,18 @@ */ import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { findTestSubject } from '@elastic/eui/lib/test'; import { FilterableEmbeddable, isErrorEmbeddable, ViewMode } from '@kbn/embeddable-plugin/public'; import { DashboardContainer } from '../embeddable/dashboard_container'; import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; import { getSampleDashboardInput } from '../test_helpers'; -import { EuiModalFooter } from '@elastic/eui'; -import { FiltersNotificationModal, FiltersNotificationProps } from './filters_notification_modal'; +import { EuiPopover } from '@elastic/eui'; +import { + FiltersNotificationPopover, + FiltersNotificationProps, +} from './filters_notification_popover'; import { ContactCardEmbeddable, ContactCardEmbeddableFactory, @@ -53,55 +57,36 @@ describe('LibraryNotificationPopover', () => { }); defaultProps = { + icon: 'test', context: { embeddable: contactCardEmbeddable }, displayName: 'test display', id: 'testId', editPanelAction: { execute: jest.fn(), } as unknown as FiltersNotificationProps['editPanelAction'], - onClose: jest.fn(), }; }); function mountComponent(props?: Partial) { - return mountWithIntl(); + return mountWithIntl(); } - test('show modal footer in edit mode', async () => { + test('clicking edit button executes edit panel action', async () => { embeddable.updateInput({ viewMode: ViewMode.EDIT }); - await act(async () => { - const component = mountComponent(); - const footer = component.find(EuiModalFooter); - expect(footer.exists()).toBe(true); - }); - }); + const component = mountComponent(); - test('hide modal footer in view mode', async () => { - embeddable.updateInput({ viewMode: ViewMode.VIEW }); await act(async () => { - const component = mountComponent(); - const footer = component.find(EuiModalFooter); - expect(footer.exists()).toBe(false); + findTestSubject(component, `embeddablePanelNotification-${defaultProps.id}`).simulate( + 'click' + ); }); - }); - - test('clicking edit button executes edit panel action', async () => { - embeddable.updateInput({ viewMode: ViewMode.EDIT }); await act(async () => { - const component = mountComponent(); - const editButton = findTestSubject(component, 'filtersNotificationModal__editButton'); - editButton.simulate('click'); - expect(defaultProps.editPanelAction.execute).toHaveBeenCalled(); + component.update(); }); - }); - test('clicking close button calls onClose', async () => { - embeddable.updateInput({ viewMode: ViewMode.EDIT }); - await act(async () => { - const component = mountComponent(); - const editButton = findTestSubject(component, 'filtersNotificationModal__closeButton'); - editButton.simulate('click'); - expect(defaultProps.onClose).toHaveBeenCalled(); - }); + const popover = component.find(EuiPopover); + const editButton = findTestSubject(popover, 'filtersNotificationModal__editButton'); + editButton.simulate('click'); + expect(defaultProps.editPanelAction.execute).toHaveBeenCalled(); }); }); diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_popover.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_popover.tsx new file mode 100644 index 0000000000000..974c7280f8968 --- /dev/null +++ b/src/plugins/dashboard/public/application/actions/filters_notification_popover.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState } from 'react'; + +import { + EuiButton, + EuiPopover, + EuiFlexItem, + EuiFlexGroup, + EuiButtonIcon, + EuiPopoverTitle, + EuiPopoverFooter, +} from '@elastic/eui'; +import { EditPanelAction } from '@kbn/embeddable-plugin/public'; + +import { dashboardFilterNotificationAction } from '../../dashboard_strings'; +import { FiltersNotificationActionContext } from './filters_notification_action'; +import { FiltersNotificationPopoverContents } from './filters_notification_popover_contents'; + +export interface FiltersNotificationProps { + context: FiltersNotificationActionContext; + editPanelAction: EditPanelAction; + displayName: string; + icon: string; + id: string; +} + +export function FiltersNotificationPopover({ + editPanelAction, + displayName, + context, + icon, + id, +}: FiltersNotificationProps) { + const { embeddable } = context; + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + return ( + setIsPopoverOpen(!isPopoverOpen)} + data-test-subj={`embeddablePanelNotification-${id}`} + aria-label={displayName} + /> + } + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + anchorPosition="upCenter" + > + {displayName} + + + + + editPanelAction.execute({ embeddable })} + > + {dashboardFilterNotificationAction.getEditButtonTitle()} + + + + + + ); +} diff --git a/src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx b/src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx new file mode 100644 index 0000000000000..b3c37f40d6c6c --- /dev/null +++ b/src/plugins/dashboard/public/application/actions/filters_notification_popover_contents.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo, useState } from 'react'; +import useMount from 'react-use/lib/useMount'; + +import { EuiCodeBlock, EuiFlexGroup, EuiForm, EuiFormRow, EuiLoadingContent } from '@elastic/eui'; +import { FilterableEmbeddable, IEmbeddable } from '@kbn/embeddable-plugin/public'; +import { FilterItems } from '@kbn/unified-search-plugin/public'; +import { css } from '@emotion/react'; +import { + type AggregateQuery, + type Filter, + getAggregateQueryMode, + isOfQueryType, +} from '@kbn/es-query'; + +import { FiltersNotificationActionContext } from './filters_notification_action'; +import { dashboardFilterNotificationAction } from '../../dashboard_strings'; +import { DashboardContainer } from '../embeddable'; + +export interface FiltersNotificationProps { + context: FiltersNotificationActionContext; +} + +export function FiltersNotificationPopoverContents({ context }: FiltersNotificationProps) { + const { embeddable } = context; + const [isLoading, setIsLoading] = useState(true); + const [filters, setFilters] = useState([]); + const [queryString, setQueryString] = useState(''); + const [queryLanguage, setQueryLanguage] = useState<'sql' | 'esql' | undefined>(); + + const dataViews = useMemo( + () => (embeddable.getRoot() as DashboardContainer)?.getAllDataViews(), + [embeddable] + ); + + useMount(() => { + Promise.all([ + (embeddable as IEmbeddable & FilterableEmbeddable).getFilters(), + (embeddable as IEmbeddable & FilterableEmbeddable).getQuery(), + ]).then(([embeddableFilters, embeddableQuery]) => { + setFilters(embeddableFilters); + if (embeddableQuery) { + if (isOfQueryType(embeddableQuery)) { + setQueryString(embeddableQuery.query as string); + } else { + const language = getAggregateQueryMode(embeddableQuery); + setQueryLanguage(language); + setQueryString(embeddableQuery[language as keyof AggregateQuery]); + } + } + setIsLoading(false); + }); + }); + + return ( + <> + {isLoading ? ( + + ) : ( + + {queryString !== '' && ( + + + {queryString} + + + )} + {filters && filters.length > 0 && ( + + + + + + )} + + )} + + ); +} diff --git a/src/plugins/dashboard/public/application/actions/index.ts b/src/plugins/dashboard/public/application/actions/index.ts index a238ce05e1017..7793c28037544 100644 --- a/src/plugins/dashboard/public/application/actions/index.ts +++ b/src/plugins/dashboard/public/application/actions/index.ts @@ -6,11 +6,7 @@ * Side Public License, v 1. */ -import { - CONTEXT_MENU_TRIGGER, - PANEL_BADGE_TRIGGER, - PANEL_NOTIFICATION_TRIGGER, -} from '@kbn/embeddable-plugin/public'; +import { CONTEXT_MENU_TRIGGER, PANEL_NOTIFICATION_TRIGGER } from '@kbn/embeddable-plugin/public'; import { CoreStart } from '@kbn/core/public'; import { getSavedObjectFinder } from '@kbn/saved-objects-plugin/public'; @@ -22,7 +18,7 @@ import { ReplacePanelAction } from './replace_panel_action'; import { AddToLibraryAction } from './add_to_library_action'; import { CopyToDashboardAction } from './copy_to_dashboard_action'; import { UnlinkFromLibraryAction } from './unlink_from_library_action'; -import { FiltersNotificationBadge } from './filters_notification_badge'; +import { FiltersNotificationAction } from './filters_notification_action'; import { LibraryNotificationAction } from './library_notification_action'; interface BuildAllDashboardActionsProps { @@ -48,14 +44,14 @@ export const buildAllDashboardActions = async ({ uiActions.registerAction(changeViewAction); uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction.id); - const panelLevelFiltersNotification = new FiltersNotificationBadge(); - uiActions.registerAction(panelLevelFiltersNotification); - uiActions.attachAction(PANEL_BADGE_TRIGGER, panelLevelFiltersNotification.id); - const expandPanelAction = new ExpandPanelAction(); uiActions.registerAction(expandPanelAction); uiActions.attachAction(CONTEXT_MENU_TRIGGER, expandPanelAction.id); + const panelLevelFiltersNotificationAction = new FiltersNotificationAction(); + uiActions.registerAction(panelLevelFiltersNotificationAction); + uiActions.attachAction(PANEL_NOTIFICATION_TRIGGER, panelLevelFiltersNotificationAction.id); + if (share) { const ExportCSVPlugin = new ExportCSVAction(); uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, ExportCSVPlugin); diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index fcb7f3cb2a7c1..73c1969ae1996 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -191,7 +191,7 @@ export const dashboardReplacePanelAction = { }), }; -export const dashboardFilterNotificationBadge = { +export const dashboardFilterNotificationAction = { getDisplayName: () => i18n.translate('dashboard.panel.filters', { defaultMessage: 'Panel filters', diff --git a/src/plugins/data/common/search/aggs/utils/parse_time_shift.test.ts b/src/plugins/data/common/search/aggs/utils/parse_time_shift.test.ts new file mode 100644 index 0000000000000..03c1b901580c8 --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/parse_time_shift.test.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import moment from 'moment'; +import { + isAbsoluteTimeShift, + parseAbsoluteTimeShift, + parseTimeShift, + REASON_IDS, + validateAbsoluteTimeShift, +} from './parse_time_shift'; + +describe('parse time shifts', () => { + describe('relative time shifts', () => { + it('should return valid duration for valid units', () => { + for (const unit of ['s', 'm', 'h', 'd', 'w', 'M', 'y']) { + expect(moment.isDuration(parseTimeShift(`1${unit}`))).toBeTruthy(); + } + }); + + it('should return previous for the previous string', () => { + expect(parseTimeShift('previous')).toBe('previous'); + }); + + it('should return "invalid" for anything else', () => { + for (const value of ['1a', 's', 'non-valid-string']) { + expect(parseTimeShift(value)).toBe('invalid'); + } + }); + }); + + describe('absolute time shifts', () => { + const dateString = '2022-11-02T00:00:00.000Z'; + const futureDateString = '3022-11-02T00:00:00.000Z'; + + function applyTimeZone(zone: string = 'Z') { + return dateString.replace('Z', zone); + } + describe('isAbsoluteTimeShift', () => { + it('should return true for a valid absoluteTimeShift string', () => { + for (const anchor of ['startAt', 'endAt']) { + expect(isAbsoluteTimeShift(`${anchor}(${dateString})`)).toBeTruthy(); + } + }); + + it('should return false for no string passed', () => { + expect(isAbsoluteTimeShift()).toBeFalsy(); + }); + + // that's ok, the function is used to distinguish from the relative shifts + it('should perform only a shallow check on the string', () => { + expect(isAbsoluteTimeShift('startAt(aaaaa)')).toBeTruthy(); + }); + }); + + describe('validateAbsoluteTimeShift', () => { + it('should return no error for valid time shifts', () => { + for (const anchor of ['startAt', 'endAt']) { + expect( + validateAbsoluteTimeShift(`${anchor}(${dateString})`, { + from: moment(dateString).add('5', 'd').toISOString(), + to: moment(dateString).add('6', 'd').toISOString(), + }) + ).toBeUndefined(); + } + }); + + it('should return no error for valid time shifts if no time range is passed', () => { + for (const anchor of ['startAt', 'endAt']) { + expect(validateAbsoluteTimeShift(`${anchor}(${dateString})`)).toBeUndefined(); + // This will pass as the range checks are relaxed without the second argument passed + expect(validateAbsoluteTimeShift(`${anchor}(${futureDateString})`)).toBeUndefined(); + } + }); + + it('should return an error if the string value is not an absolute time shift', () => { + for (const val of ['startAt()', 'endAt()', '1d', 'aaa']) { + expect(validateAbsoluteTimeShift(val)).toBe(REASON_IDS.notAbsoluteTimeShift); + } + }); + + it('should return an error if the passed date is invalid', () => { + for (const val of [ + 'startAt(a)', + 'endAt(a)', + 'startAt(2022)', + 'startAt(2022-11-02T00:00:00.000)', + 'endAt(2022-11-02)', + ]) { + expect(validateAbsoluteTimeShift(val)).toBe(REASON_IDS.invalidDate); + } + }); + + it('should return an error if dateRange is passed and the shift is after that', () => { + for (const anchor of ['startAt', 'endAt']) { + expect( + validateAbsoluteTimeShift(`${anchor}(${futureDateString})`, { + from: moment(dateString).subtract('1', 'd').toISOString(), + to: moment(dateString).add('1', 'd').toISOString(), + }) + ).toBe(REASON_IDS.shiftAfterTimeRange); + } + }); + + it('should return no error for dates with non-UTC offset', () => { + for (const anchor of ['startAt', 'endAt']) { + for (const offset of ['Z', '+01:00', '-12:00', '-05']) { + expect( + validateAbsoluteTimeShift(`${anchor}(${applyTimeZone(offset)})`, { + from: moment(dateString).add('5', 'd').toISOString(), + to: moment(dateString).add('6', 'd').toISOString(), + }) + ).toBeUndefined(); + } + } + }); + }); + + describe('parseAbsoluteTimeShift', () => { + it('should return an error if no time range is passed', () => { + for (const anchor of ['startAt', 'endAt']) { + expect(parseAbsoluteTimeShift(`${anchor}(${dateString})`, undefined)).toEqual({ + value: 'invalid', + reason: REASON_IDS.missingTimerange, + }); + } + }); + + it('should return an error for invalid dates', () => { + for (const invalidDates of [ + 'startAt(a)', + 'endAt(a)', + 'startAt(2022)', + 'startAt(2022-11-02T00:00:00.000)', + 'endAt(2022-11-02)', + '1d', + 'aaa', + `startAt(${futureDateString})`, + ]) { + expect( + parseAbsoluteTimeShift(invalidDates, { + from: moment(dateString).add('5', 'd').toISOString(), + to: moment(dateString).add('6', 'd').toISOString(), + }).value + ).toBe('invalid'); + } + }); + + it('should return no reason for a valid absolute time shift', () => { + for (const anchor of ['startAt', 'endAt']) { + expect( + parseAbsoluteTimeShift(`${anchor}(${dateString})`, { + from: moment(dateString).add('5', 'd').toISOString(), + to: moment(dateString).add('6', 'd').toISOString(), + }).reason + ).toBe(null); + } + }); + }); + }); +}); diff --git a/src/plugins/data/common/search/aggs/utils/parse_time_shift.ts b/src/plugins/data/common/search/aggs/utils/parse_time_shift.ts index 91379ea054de3..eb0241865eb3e 100644 --- a/src/plugins/data/common/search/aggs/utils/parse_time_shift.ts +++ b/src/plugins/data/common/search/aggs/utils/parse_time_shift.ts @@ -6,24 +6,140 @@ * Side Public License, v 1. */ import moment from 'moment'; +import { TimeRange } from '../../../types'; -const allowedUnits = ['s', 'm', 'h', 'd', 'w', 'M', 'y'] as const; -type AllowedUnit = typeof allowedUnits[number]; +const ALLOWED_UNITS = ['s', 'm', 'h', 'd', 'w', 'M', 'y'] as const; +const ANCHORED_TIME_SHIFT_REGEXP = /^(startAt|endAt)\((.+)\)$/; +const DURATION_REGEXP = /^(\d+)\s*(\w)$/; +const INVALID_DATE = 'invalid'; +const PREVIOUS_DATE = 'previous'; +const START_AT_ANCHOR = 'startAt'; + +type AllowedUnit = typeof ALLOWED_UNITS[number]; +type PreviousDateType = typeof PREVIOUS_DATE; +type InvalidDateType = typeof INVALID_DATE; + +// The ISO8601 format supports also partial date strings as described here: +// https://momentjs.com/docs/#/parsing/string/ +// But in this specific case we want to enforce the full version of the ISO8601 +// which is build in this case with moment special HTML5 format + the timezone support +const LONG_ISO8601_LIKE_FORMAT = moment.HTML5_FMT.DATETIME_LOCAL_MS + 'Z'; /** * This method parses a string into a time shift duration. * If parsing fails, 'invalid' is returned. * Allowed values are the string 'previous' and an integer followed by the units s,m,h,d,w,M,y * */ -export const parseTimeShift = (val: string): moment.Duration | 'previous' | 'invalid' => { +export const parseTimeShift = ( + val: string +): moment.Duration | PreviousDateType | InvalidDateType => { const trimmedVal = val.trim(); - if (trimmedVal === 'previous') { - return 'previous'; + if (trimmedVal === PREVIOUS_DATE) { + return PREVIOUS_DATE; } - const [, amount, unit] = trimmedVal.match(/^(\d+)\s*(\w)$/) || []; + const [, amount, unit] = trimmedVal.match(DURATION_REGEXP) || []; const parsedAmount = Number(amount); - if (Number.isNaN(parsedAmount) || !allowedUnits.includes(unit as AllowedUnit)) { - return 'invalid'; + if (Number.isNaN(parsedAmount) || !ALLOWED_UNITS.includes(unit as AllowedUnit)) { + return INVALID_DATE; } return moment.duration(Number(amount), unit as AllowedUnit); }; + +/** + * Check function to detect an absolute time shift. + * The check is performed only on the string format and the timestamp is not validated: + * use the validateAbsoluteTimeShift fucntion to perform more in depth checks + * @param val the string to parse (it assumes it has been trimmed already) + * @returns true if an absolute time shift + */ +export const isAbsoluteTimeShift = (val?: string) => { + return val != null && ANCHORED_TIME_SHIFT_REGEXP.test(val); +}; + +export const REASON_IDS = { + missingTimerange: 'missingTimerange', + notAbsoluteTimeShift: 'notAbsoluteTimeShift', + invalidDate: 'invalidDate', + shiftAfterTimeRange: 'shiftAfterTimeRange', +} as const; + +export type REASON_ID_TYPES = keyof typeof REASON_IDS; + +/** + * Parses an absolute time shift string and returns its equivalent duration + * @param val the string to parse + * @param timeRange the current date histogram interval + * @returns + */ +export const parseAbsoluteTimeShift = ( + val: string, + timeRange: TimeRange | undefined +): + | { value: moment.Duration; reason: null } + | { value: InvalidDateType; reason: REASON_ID_TYPES } => { + const trimmedVal = val.trim(); + if (timeRange == null) { + return { + value: INVALID_DATE, + reason: REASON_IDS.missingTimerange, + }; + } + const error = validateAbsoluteTimeShift(trimmedVal, timeRange); + if (error) { + return { + value: INVALID_DATE, + reason: error, + }; + } + const { anchor, timestamp } = extractTokensFromAbsTimeShift(trimmedVal); + // the regexp test above will make sure anchor and timestamp are both strings + // now be very strict on the format + const tsMoment = moment(timestamp, LONG_ISO8601_LIKE_FORMAT, true); + // workout how long is the ref time range + const duration = moment(timeRange.to).diff(moment(timeRange.from)); + // pick the end of the absolute range now + const absRangeEnd = anchor === START_AT_ANCHOR ? tsMoment.add(duration) : tsMoment; + // return (ref end date - shift end date) + return { value: moment.duration(moment(timeRange.to).diff(absRangeEnd)), reason: null }; +}; + +/** + * Fucntion to extract the anchor and timestamp tokens from an absolute time shift + * @param val absolute time shift string + * @returns the anchor and timestamp strings + */ +function extractTokensFromAbsTimeShift(val: string) { + const [, anchor, timestamp] = val.match(ANCHORED_TIME_SHIFT_REGEXP) || []; + return { anchor, timestamp }; +} +/** + * Relaxed version of the parsing validation + * This version of the validation applies the timeRange validation only when passed + * @param val + * @param timeRange + * @returns the reason id if the absolute shift is not valid, undefined otherwise + */ +export function validateAbsoluteTimeShift( + val: string, + timeRange?: TimeRange +): REASON_ID_TYPES | undefined { + const trimmedVal = val.trim(); + if (!isAbsoluteTimeShift(trimmedVal)) { + return REASON_IDS.notAbsoluteTimeShift; + } + const { anchor, timestamp } = extractTokensFromAbsTimeShift(trimmedVal); + // the regexp test above will make sure anchor and timestamp are both strings + // now be very strict on the format + const tsMoment = moment(timestamp, LONG_ISO8601_LIKE_FORMAT, true); + if (!tsMoment.isValid()) { + return REASON_IDS.invalidDate; + } + if (timeRange) { + const duration = moment(timeRange.to).diff(moment(timeRange.from)); + if ( + (anchor === START_AT_ANCHOR && tsMoment.isAfter(timeRange.from)) || + tsMoment.subtract(duration).isAfter(timeRange.from) + ) + return REASON_IDS.shiftAfterTimeRange; + } +} diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx index 13e1287022f3d..026ca50f9f818 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx @@ -168,6 +168,16 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) }, }); break; + case FetchStatus.ERROR: + dispatchSidebarStateAction({ + type: DiscoverSidebarReducerActionType.DOCUMENTS_LOADED, + payload: { + dataView: selectedDataViewRef.current, + fieldCounts: {}, + isPlainRecord: isPlainRecordType, + }, + }); + break; default: break; } diff --git a/src/plugins/embeddable/public/lib/containers/container.ts b/src/plugins/embeddable/public/lib/containers/container.ts index 843d3a7cd5c99..278494b9df559 100644 --- a/src/plugins/embeddable/public/lib/containers/container.ts +++ b/src/plugins/embeddable/public/lib/containers/container.ts @@ -398,7 +398,7 @@ export abstract class Container< } } - private async createAndSaveEmbeddable< + protected async createAndSaveEmbeddable< TEmbeddableInput extends EmbeddableInput = EmbeddableInput, TEmbeddable extends IEmbeddable = IEmbeddable >(type: string, panelState: PanelState) { diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.tsx index 93a2c6333f9cc..bfae4b052f5d9 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor.tsx @@ -443,7 +443,7 @@ export const CodeEditor: React.FC = ({ ) : null} = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, - 'visualization:visualize:legacyPieChartsLibrary': { - type: 'boolean', - _meta: { description: 'Non-default value of setting.' }, - }, 'visualization:visualize:legacyHeatmapChartsLibrary': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 4365e2a1dd72e..a57bb1ef016fd 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -27,7 +27,6 @@ export interface UsageStats { 'autocomplete:useTimeRange': boolean; 'autocomplete:valueSuggestionMethod': string; 'search:timeout': number; - 'visualization:visualize:legacyPieChartsLibrary': boolean; 'visualization:visualize:legacyHeatmapChartsLibrary': boolean; 'doc_table:legacy': boolean; 'discover:modifyColumnsOnSwitch': boolean; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index c2b0971827d14..e3bd69c3bd243 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -8844,12 +8844,6 @@ "description": "Non-default value of setting." } }, - "visualization:visualize:legacyPieChartsLibrary": { - "type": "boolean", - "_meta": { - "description": "Non-default value of setting." - } - }, "visualization:visualize:legacyHeatmapChartsLibrary": { "type": "boolean", "_meta": { diff --git a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx index 88140ef3d46b3..ab9e8b0374783 100644 --- a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx +++ b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx @@ -8,7 +8,7 @@ import React, { useRef, memo, useEffect, useState, useCallback } from 'react'; import classNames from 'classnames'; -import { EsqlLang, monaco } from '@kbn/monaco'; +import { SQLLang, monaco } from '@kbn/monaco'; import type { AggregateQuery } from '@kbn/es-query'; import { getAggregateQueryMode } from '@kbn/es-query'; import { @@ -72,7 +72,7 @@ const languageId = (language: string) => { switch (language) { case 'sql': default: { - return EsqlLang.ID; + return SQLLang.ID; } } }; diff --git a/src/plugins/vis_types/pie/common/index.ts b/src/plugins/vis_types/pie/common/index.ts index a02a2b2ba10f2..1aa1680530b32 100644 --- a/src/plugins/vis_types/pie/common/index.ts +++ b/src/plugins/vis_types/pie/common/index.ts @@ -7,4 +7,3 @@ */ export const DEFAULT_PERCENT_DECIMALS = 2; -export const LEGACY_PIE_CHARTS_LIBRARY = 'visualization:visualize:legacyPieChartsLibrary'; diff --git a/src/plugins/vis_types/pie/public/plugin.ts b/src/plugins/vis_types/pie/public/plugin.ts index b4a8c0e3a2a69..4eccefb38407f 100644 --- a/src/plugins/vis_types/pie/public/plugin.ts +++ b/src/plugins/vis_types/pie/public/plugin.ts @@ -12,7 +12,6 @@ import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { LEGACY_PIE_CHARTS_LIBRARY } from '../common'; import { pieVisType } from './vis_type'; import { setDataViewsStart } from './services'; @@ -49,14 +48,12 @@ export class VisTypePiePlugin { core: CoreSetup, { visualizations, charts, usageCollection }: VisTypePieSetupDependencies ) { - if (!core.uiSettings.get(LEGACY_PIE_CHARTS_LIBRARY, false)) { - visualizations.createBaseVisualization( - pieVisType({ - showElasticChartsOptions: true, - palettes: charts.palettes, - }) - ); - } + visualizations.createBaseVisualization( + pieVisType({ + showElasticChartsOptions: true, + palettes: charts.palettes, + }) + ); return {}; } diff --git a/src/plugins/vis_types/pie/server/plugin.ts b/src/plugins/vis_types/pie/server/plugin.ts index 024e1ecac8e33..9e172834e8a2a 100644 --- a/src/plugins/vis_types/pie/server/plugin.ts +++ b/src/plugins/vis_types/pie/server/plugin.ts @@ -6,47 +6,10 @@ * Side Public License, v 1. */ -import { i18n } from '@kbn/i18n'; -import { schema } from '@kbn/config-schema'; - -import { CoreSetup, Plugin, UiSettingsParams } from '@kbn/core/server'; - -import { LEGACY_PIE_CHARTS_LIBRARY } from '../common'; - -export const getUiSettingsConfig: () => Record> = () => ({ - // TODO: Remove this when vislib pie is removed - // https://github.com/elastic/kibana/issues/111246 - [LEGACY_PIE_CHARTS_LIBRARY]: { - name: i18n.translate('visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name', { - defaultMessage: 'Pie legacy charts library', - }), - requiresPageReload: true, - value: false, - description: i18n.translate( - 'visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.description', - { - defaultMessage: 'Enables legacy charts library for pie charts in visualize.', - } - ), - deprecation: { - message: i18n.translate( - 'visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.deprecation', - { - defaultMessage: - 'The legacy charts library for pie in visualize is deprecated and will not be supported in a future version.', - } - ), - docLinksKey: 'visualizationSettings', - }, - category: ['visualization'], - schema: schema.boolean(), - }, -}); +import { CoreSetup, Plugin } from '@kbn/core/server'; export class VisTypePieServerPlugin implements Plugin { public setup(core: CoreSetup) { - core.uiSettings.register(getUiSettingsConfig()); - return {}; } diff --git a/src/plugins/vis_types/vislib/kibana.json b/src/plugins/vis_types/vislib/kibana.json index e12149bb5ab01..1e31c498abc58 100644 --- a/src/plugins/vis_types/vislib/kibana.json +++ b/src/plugins/vis_types/vislib/kibana.json @@ -5,10 +5,10 @@ "ui": true, "requiredPlugins": ["charts", "data", "expressions", "visualizations", "fieldFormats"], "optionalPlugins": ["usageCollection"], - "requiredBundles": ["kibanaUtils", "visTypePie", "visTypeHeatmap", "visTypeGauge", "kibanaReact"], + "requiredBundles": ["kibanaUtils", "visTypeHeatmap", "visTypeGauge", "kibanaReact"], "owner": { "name": "Vis Editors", "githubTeam": "kibana-visualizations" }, - "description": "Contains the vislib visualizations. These are the classical area/line/bar, pie, gauge/goal and heatmap charts. We want to replace them with elastic-charts." + "description": "Contains the vislib visualizations. These are the classical area/line/bar, gauge/goal and heatmap charts. We want to replace them with elastic-charts." } diff --git a/src/plugins/vis_types/vislib/public/__snapshots__/pie_fn.test.ts.snap b/src/plugins/vis_types/vislib/public/__snapshots__/pie_fn.test.ts.snap deleted file mode 100644 index 5c6bc7e8dc5d5..0000000000000 --- a/src/plugins/vis_types/vislib/public/__snapshots__/pie_fn.test.ts.snap +++ /dev/null @@ -1,50 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`interpreter/functions#pie returns an object with the correct structure 1`] = ` -Object { - "as": "vislib_vis", - "type": "render", - "value": Object { - "visConfig": Object { - "addTooltip": true, - "dimensions": Object { - "metric": Object { - "accessor": 0, - "aggType": "count", - "format": Object { - "id": "number", - }, - "params": Object {}, - }, - }, - "isDonut": true, - "labels": Object { - "last_level": true, - "show": false, - "truncate": 100, - "values": true, - }, - "legendDisplay": "show", - "legendPosition": "right", - "type": "pie", - }, - "visData": Object { - "hits": 1, - "names": Array [ - "Count", - ], - "raw": Object { - "columns": Array [], - "rows": Array [], - }, - "slices": Object { - "children": Array [], - }, - "tooltipFormatter": Object { - "id": "number", - }, - }, - "visType": "pie", - }, -} -`; diff --git a/src/plugins/vis_types/vislib/public/__snapshots__/to_ast_pie.test.ts.snap b/src/plugins/vis_types/vislib/public/__snapshots__/to_ast_pie.test.ts.snap deleted file mode 100644 index cf9e9aad3a0c4..0000000000000 --- a/src/plugins/vis_types/vislib/public/__snapshots__/to_ast_pie.test.ts.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`vislib pie vis toExpressionAst function should match basic snapshot 1`] = ` -Object { - "addArgument": [Function], - "arguments": Object { - "visConfig": Array [ - "{\\"type\\":\\"pie\\",\\"addTooltip\\":true,\\"legendDisplay\\":\\"show\\",\\"legendPosition\\":\\"right\\",\\"legendSize\\":\\"large\\",\\"isDonut\\":true,\\"labels\\":{\\"show\\":true,\\"values\\":true,\\"last_level\\":true,\\"truncate\\":100},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\"},\\"params\\":{},\\"aggType\\":\\"count\\",\\"aggId\\":\\"1\\",\\"aggParams\\":{}},\\"buckets\\":[{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{},\\"aggType\\":\\"terms\\",\\"aggId\\":\\"2\\",\\"aggParams\\":{\\"field\\":\\"Carrier\\",\\"orderBy\\":\\"1\\",\\"order\\":\\"desc\\",\\"size\\":5,\\"otherBucket\\":false,\\"otherBucketLabel\\":\\"Other\\",\\"missingBucket\\":false,\\"missingBucketLabel\\":\\"Missing\\"}}]}}", - ], - }, - "getArgument": [Function], - "name": "vislib_pie_vis", - "removeArgument": [Function], - "replaceArgument": [Function], - "toAst": [Function], - "toString": [Function], - "type": "expression_function_builder", -} -`; diff --git a/src/plugins/vis_types/vislib/public/pie.ts b/src/plugins/vis_types/vislib/public/pie.ts deleted file mode 100644 index 27996346ccb28..0000000000000 --- a/src/plugins/vis_types/vislib/public/pie.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { pieVisType } from '@kbn/vis-type-pie-plugin/public'; -import { VisTypeDefinition } from '@kbn/visualizations-plugin/public'; -import { CommonVislibParams } from './types'; -import { toExpressionAst } from './to_ast_pie'; - -export enum LegendDisplay { - SHOW = 'show', - HIDE = 'hide', - DEFAULT = 'default', -} - -export type PieVisParams = Omit & { - type: 'pie'; - isDonut: boolean; - labels: { - show: boolean; - values: boolean; - last_level: boolean; - truncate: number | null; - }; - legendDisplay: LegendDisplay; -}; - -export const pieVisTypeDefinition = { - ...pieVisType({}), - toExpressionAst, -} as VisTypeDefinition; diff --git a/src/plugins/vis_types/vislib/public/pie_fn.test.ts b/src/plugins/vis_types/vislib/public/pie_fn.test.ts deleted file mode 100644 index 55d3c8fb7e0c3..0000000000000 --- a/src/plugins/vis_types/vislib/public/pie_fn.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { Datatable } from '@kbn/expressions-plugin/common'; -import { functionWrapper } from '@kbn/expressions-plugin/common/expression_functions/specs/tests/utils'; -import { createPieVisFn } from './pie_fn'; -// @ts-ignore -import { vislibSlicesResponseHandler } from './vislib/response_handler'; - -jest.mock('./vislib/response_handler', () => ({ - vislibSlicesResponseHandler: jest.fn().mockReturnValue({ - hits: 1, - names: ['Count'], - raw: { - columns: [], - rows: [], - }, - slices: { - children: [], - }, - tooltipFormatter: { - id: 'number', - }, - }), -})); - -describe('interpreter/functions#pie', () => { - const fn = functionWrapper(createPieVisFn()); - const context = { - type: 'datatable', - rows: [{ 'col-0-1': 0 }], - columns: [{ id: 'col-0-1', name: 'Count' }], - } as unknown as Datatable; - const visConfig = { - type: 'pie', - addTooltip: true, - legendDisplay: 'show', - legendPosition: 'right', - isDonut: true, - labels: { - show: false, - values: true, - last_level: true, - truncate: 100, - }, - dimensions: { - metric: { - accessor: 0, - format: { - id: 'number', - }, - params: {}, - aggType: 'count', - }, - }, - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('returns an object with the correct structure', async () => { - const actual = await fn(context, { visConfig: JSON.stringify(visConfig) }); - expect(actual).toMatchSnapshot(); - }); - - it('calls response handler with correct values', async () => { - await fn(context, { visConfig: JSON.stringify(visConfig) }); - expect(vislibSlicesResponseHandler).toHaveBeenCalledTimes(1); - expect(vislibSlicesResponseHandler).toHaveBeenCalledWith(context, visConfig.dimensions); - }); -}); diff --git a/src/plugins/vis_types/vislib/public/pie_fn.ts b/src/plugins/vis_types/vislib/public/pie_fn.ts deleted file mode 100644 index 06d1f5f4baf9a..0000000000000 --- a/src/plugins/vis_types/vislib/public/pie_fn.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -import { ExpressionFunctionDefinition, Datatable, Render } from '@kbn/expressions-plugin/public'; - -// @ts-ignore -import { vislibSlicesResponseHandler } from './vislib/response_handler'; -import { PieVisParams } from './pie'; -import { VislibChartType } from './types'; -import { vislibVisName } from './vis_type_vislib_vis_fn'; - -export const vislibPieName = 'vislib_pie_vis'; - -interface Arguments { - visConfig: string; -} - -export interface PieRenderValue { - visType: Extract; - visData: unknown; - visConfig: PieVisParams; -} - -export type VisTypeVislibPieExpressionFunctionDefinition = ExpressionFunctionDefinition< - typeof vislibPieName, - Datatable, - Arguments, - Render ->; - -export const createPieVisFn = (): VisTypeVislibPieExpressionFunctionDefinition => ({ - name: vislibPieName, - type: 'render', - inputTypes: ['datatable'], - help: i18n.translate('visTypeVislib.functions.pie.help', { - defaultMessage: 'Pie visualization', - }), - args: { - visConfig: { - types: ['string'], - default: '"{}"', - help: 'vislib pie vis config', - }, - }, - fn(input, args, handlers) { - const visConfig = JSON.parse(args.visConfig) as PieVisParams; - const visData = vislibSlicesResponseHandler(input, visConfig.dimensions); - - if (handlers?.inspectorAdapters?.tables) { - handlers.inspectorAdapters.tables.logDatatable('default', input); - } - - return { - type: 'render', - as: vislibVisName, - value: { - visData, - visConfig, - visType: VislibChartType.Pie, - }, - }; - }, -}); diff --git a/src/plugins/vis_types/vislib/public/plugin.ts b/src/plugins/vis_types/vislib/public/plugin.ts index d67fb96f9ca1b..d734bf9b1c501 100644 --- a/src/plugins/vis_types/vislib/public/plugin.ts +++ b/src/plugins/vis_types/vislib/public/plugin.ts @@ -14,15 +14,12 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; -import { LEGACY_PIE_CHARTS_LIBRARY } from '@kbn/vis-type-pie-plugin/common'; import { LEGACY_HEATMAP_CHARTS_LIBRARY } from '@kbn/vis-type-heatmap-plugin/common'; import { LEGACY_GAUGE_CHARTS_LIBRARY } from '@kbn/vis-type-gauge-plugin/common'; import { setUsageCollectionStart } from './services'; import { heatmapVisTypeDefinition } from './heatmap'; import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn'; -import { createPieVisFn } from './pie_fn'; -import { pieVisTypeDefinition } from './pie'; import { setFormatService, setDataActions, setTheme } from './services'; import { getVislibVisRenderer } from './vis_renderer'; import { gaugeVisTypeDefinition } from './gauge'; @@ -60,12 +57,6 @@ export class VisTypeVislibPlugin expressions.registerRenderer(getVislibVisRenderer(core, charts)); expressions.registerFunction(createVisTypeVislibVisFn()); - if (core.uiSettings.get(LEGACY_PIE_CHARTS_LIBRARY, false)) { - // register vislib pie chart - visualizations.createBaseVisualization(pieVisTypeDefinition); - expressions.registerFunction(createPieVisFn()); - } - if (core.uiSettings.get(LEGACY_HEATMAP_CHARTS_LIBRARY)) { // register vislib heatmap chart visualizations.createBaseVisualization(heatmapVisTypeDefinition); diff --git a/src/plugins/vis_types/vislib/public/to_ast.ts b/src/plugins/vis_types/vislib/public/to_ast.ts index ceb938d5d72e1..be5ef60edbd2e 100644 --- a/src/plugins/vis_types/vislib/public/to_ast.ts +++ b/src/plugins/vis_types/vislib/public/to_ast.ts @@ -89,7 +89,7 @@ export const toExpressionAst = async ( const visTypeVislib = buildExpressionFunction( vislibVisName, { - type: vis.type.name as Exclude, + type: vis.type.name as VislibChartType, visConfig: JSON.stringify({ ...visConfig, dimensions }), } ); diff --git a/src/plugins/vis_types/vislib/public/to_ast_pie.test.ts b/src/plugins/vis_types/vislib/public/to_ast_pie.test.ts deleted file mode 100644 index 9cb57c8086db1..0000000000000 --- a/src/plugins/vis_types/vislib/public/to_ast_pie.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Vis } from '@kbn/visualizations-plugin/public'; -import { buildExpression } from '@kbn/expressions-plugin/public'; - -import { PieVisParams } from './pie'; -import { samplePieVis } from '@kbn/vis-type-pie-plugin/public/sample_vis.test.mocks'; -import { toExpressionAst } from './to_ast_pie'; - -jest.mock('@kbn/expressions-plugin/public', () => ({ - ...(jest.requireActual('@kbn/expressions-plugin/public') as any), - buildExpression: jest.fn().mockImplementation(() => ({ - toAst: () => ({ - type: 'expression', - chain: [], - }), - })), -})); - -describe('vislib pie vis toExpressionAst function', () => { - let vis: Vis; - - const params = { - timefilter: {}, - timeRange: {}, - abortSignal: {}, - } as any; - - beforeEach(() => { - vis = samplePieVis as any; - }); - - it('should match basic snapshot', () => { - toExpressionAst(vis, params); - const [builtExpression] = (buildExpression as jest.Mock).mock.calls[0][0]; - - expect(builtExpression).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/vis_types/vislib/public/to_ast_pie.ts b/src/plugins/vis_types/vislib/public/to_ast_pie.ts deleted file mode 100644 index 3302130df0134..0000000000000 --- a/src/plugins/vis_types/vislib/public/to_ast_pie.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getVisSchemas, VisToExpressionAst } from '@kbn/visualizations-plugin/public'; -import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugin/public'; - -import { PieVisParams } from './pie'; -import { vislibPieName, VisTypeVislibPieExpressionFunctionDefinition } from './pie_fn'; - -export const toExpressionAst: VisToExpressionAst = async (vis, params) => { - const schemas = getVisSchemas(vis, params); - const visConfig = { - ...vis.params, - dimensions: { - metric: schemas.metric[0], - buckets: schemas.segment, - splitRow: schemas.split_row, - splitColumn: schemas.split_column, - }, - }; - - const visTypePie = buildExpressionFunction( - vislibPieName, - { - visConfig: JSON.stringify(visConfig), - } - ); - - const ast = buildExpression([visTypePie]); - - return ast.toAst(); -}; diff --git a/src/plugins/vis_types/vislib/public/types.ts b/src/plugins/vis_types/vislib/public/types.ts index dd4c724be1d17..de6d9b4f4ab2d 100644 --- a/src/plugins/vis_types/vislib/public/types.ts +++ b/src/plugins/vis_types/vislib/public/types.ts @@ -37,7 +37,6 @@ export const GaugeType = Object.freeze({ export type GaugeType = $Values; export const VislibChartType = Object.freeze({ - Pie: 'pie' as const, Heatmap: 'heatmap' as const, Gauge: 'gauge' as const, Goal: 'goal' as const, diff --git a/src/plugins/vis_types/vislib/public/vis_controller.tsx b/src/plugins/vis_types/vislib/public/vis_controller.tsx index 50c72a46e818a..cba9e7f0d71da 100644 --- a/src/plugins/vis_types/vislib/public/vis_controller.tsx +++ b/src/plugins/vis_types/vislib/public/vis_controller.tsx @@ -16,7 +16,6 @@ import { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public'; import { VisTypeVislibCoreSetup } from './plugin'; import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend'; import { BasicVislibParams } from './types'; -import { LegendDisplay, PieVisParams } from './pie'; const legendClassName = { top: 'vislib--legend-top', @@ -63,7 +62,7 @@ export const createVislibVisController = ( async render( esResponse: any, - visParams: BasicVislibParams | PieVisParams, + visParams: BasicVislibParams, handlers: IInterpreterRenderHandlers, renderComplete: (() => void) | undefined ): Promise { @@ -123,7 +122,7 @@ export const createVislibVisController = ( mountLegend( visData: unknown, - visParams: BasicVislibParams | PieVisParams, + visParams: BasicVislibParams, fireEvent: IInterpreterRenderHandlers['event'], uiState?: PersistedState ) { @@ -155,15 +154,8 @@ export const createVislibVisController = ( } } - showLegend(visParams: BasicVislibParams | PieVisParams) { - if (this.arePieVisParams(visParams)) { - return visParams.legendDisplay === LegendDisplay.SHOW; - } + showLegend(visParams: BasicVislibParams) { return visParams.addLegend ?? false; } - - arePieVisParams(visParams: BasicVislibParams | PieVisParams): visParams is PieVisParams { - return Object.values(LegendDisplay).includes((visParams as PieVisParams).legendDisplay); - } }; }; diff --git a/src/plugins/vis_types/vislib/public/vis_renderer.tsx b/src/plugins/vis_types/vislib/public/vis_renderer.tsx index 767cd6275b9b1..d96e5508a5be3 100644 --- a/src/plugins/vis_types/vislib/public/vis_renderer.tsx +++ b/src/plugins/vis_types/vislib/public/vis_renderer.tsx @@ -17,7 +17,6 @@ import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import { VisTypeVislibCoreSetup } from './plugin'; import { VislibRenderValue, vislibVisName } from './vis_type_vislib_vis_fn'; import { VislibChartType } from './types'; -import { PieRenderValue } from './pie_fn'; const VislibWrapper = lazy(() => import('./vis_wrapper')); @@ -35,7 +34,7 @@ function shouldShowNoResultsMessage(visData: any, visType: VislibChartType): boo export const getVislibVisRenderer: ( core: VisTypeVislibCoreSetup, charts: ChartsPluginSetup -) => ExpressionRenderDefinition = (core, charts) => ({ +) => ExpressionRenderDefinition = (core, charts) => ({ name: vislibVisName, displayName: 'Vislib visualization', reuseDomNode: true, diff --git a/src/plugins/vis_types/vislib/public/vis_type_vislib_vis_fn.ts b/src/plugins/vis_types/vislib/public/vis_type_vislib_vis_fn.ts index 26385d70a9aaa..0cc8c59cd6f24 100644 --- a/src/plugins/vis_types/vislib/public/vis_type_vislib_vis_fn.ts +++ b/src/plugins/vis_types/vislib/public/vis_type_vislib_vis_fn.ts @@ -17,12 +17,12 @@ import { BasicVislibParams, VislibChartType } from './types'; export const vislibVisName = 'vislib_vis'; interface Arguments { - type: Exclude; + type: VislibChartType; visConfig: string; } export interface VislibRenderValue { - visType: Exclude; + visType: VislibChartType; visData: unknown; visConfig: BasicVislibParams; } @@ -54,7 +54,7 @@ export const createVisTypeVislibVisFn = (): VisTypeVislibExpressionFunctionDefin }, }, fn(context, args, handlers) { - const visType = args.type as Exclude; + const visType = args.type as VislibChartType; const visConfig = JSON.parse(args.visConfig) as BasicVislibParams; const visData = vislibSeriesResponseHandler(context, visConfig.dimensions); diff --git a/src/plugins/vis_types/vislib/public/vis_wrapper.tsx b/src/plugins/vis_types/vislib/public/vis_wrapper.tsx index 5a68e74b5a485..50e1b266963db 100644 --- a/src/plugins/vis_types/vislib/public/vis_wrapper.tsx +++ b/src/plugins/vis_types/vislib/public/vis_wrapper.tsx @@ -19,12 +19,11 @@ import { METRIC_TYPE } from '@kbn/analytics'; import { VislibRenderValue } from './vis_type_vislib_vis_fn'; import { createVislibVisController, VislibVisController } from './vis_controller'; import { VisTypeVislibCoreSetup } from './plugin'; -import { PieRenderValue } from './pie_fn'; import './index.scss'; import { getUsageCollectionStart } from './services'; -type VislibWrapperProps = (VislibRenderValue | PieRenderValue) & { +type VislibWrapperProps = VislibRenderValue & { core: VisTypeVislibCoreSetup; charts: ChartsPluginSetup; handlers: IInterpreterRenderHandlers; diff --git a/src/plugins/vis_types/vislib/public/vislib/VISLIB.md b/src/plugins/vis_types/vislib/public/vislib/VISLIB.md index 53e83bf15bd81..3e4641f319875 100644 --- a/src/plugins/vis_types/vislib/public/vislib/VISLIB.md +++ b/src/plugins/vis_types/vislib/public/vislib/VISLIB.md @@ -1,6 +1,6 @@ # Charts supported -Vislib supports the gauge/goal charts from the aggregation-based visualizations. It also contains the legacy implemementation of the pie and heatmap chart (enabled by the visualization:visualize:legacyPieChartsLibrary and visualization:visualize:legacyHeatmapChartsLibrary advanced setting respectively). +Vislib supports the gauge/goal charts from the aggregation-based visualizations. It also contains the legacy implemementation of the heatmap chart (enabled by the visualization:visualize:legacyHeatmapChartsLibrary advanced setting). # General overview @@ -12,11 +12,10 @@ Vislib supports the gauge/goal charts from the aggregation-based visualizations. ## Visualizations -Each base vis type (`lib/types`) can have a different layout defined (`lib/layout`) and different building blocks (pie charts dont have axes for example) +Each base vis type (`lib/types`) can have a different layout defined (`lib/layout`) and different building blocks All base visualizations extend from `visualizations/_chart` -### Pie chart ### Map diff --git a/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx b/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx index fedeb03cdde28..0c19b2dbaf67f 100644 --- a/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx +++ b/src/plugins/vis_types/vislib/public/vislib/components/legend/legend.tsx @@ -20,7 +20,6 @@ import { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public'; import { getDataActions } from '../../../services'; import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models'; import { VisLegendItem } from './legend_item'; -import { getPieNames } from './pie_utils'; import { BasicVislibParams } from '../../../types'; export interface VisLegendProps { @@ -160,7 +159,7 @@ export class VisLegend extends PureComponent { if (!data) return []; data = data.columns || data.rows || [data]; - labels = type === 'pie' ? getPieNames(data) : this.getSeriesLabels(data); + labels = this.getSeriesLabels(data); } this.setFilterableLabels(labels); diff --git a/src/plugins/vis_types/vislib/public/vislib/components/legend/pie_utils.ts b/src/plugins/vis_types/vislib/public/vislib/components/legend/pie_utils.ts deleted file mode 100644 index 0912adaf9f548..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/components/legend/pie_utils.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; - -/** - * Returns an array of names ordered by appearance in the nested array - * of objects - * - * > Duplicated utilty method from vislib Data class to decouple `vislib_vis_legend` from `vislib` - * - * @see src/plugins/vis_types/vislib/public/vislib/lib/data.js - * - * @returns {Array} Array of unique names (strings) - */ -export function getPieNames(data: any[]): string[] { - const names: string[] = []; - - _.forEach(data, function (obj) { - const columns = obj.raw ? obj.raw.columns : undefined; - _.forEach(getNames(obj, columns), function (name) { - names.push(name); - }); - }); - - return _.uniqBy(names, 'label'); -} - -/** - * Flattens hierarchical data into an array of objects with a name and index value. - * The indexed value determines the order of nesting in the data. - * Returns an array with names sorted by the index value. - * - * @param data {Object} Chart data object - * @param columns {Object} Contains formatter information - * @returns {Array} Array of names (strings) - */ -function getNames(data: any, columns: any): string[] { - const slices = data.slices; - - if (slices.children) { - const namedObj = returnNames(slices.children, 0, columns); - - return _(namedObj) - .sortBy(function (obj) { - return obj.index; - }) - .uniqBy(function (d) { - return d.label; - }) - .value(); - } - - return []; -} - -/** - * Helper function for getNames - * Returns an array of objects with a name (key) value and an index value. - * The index value allows us to sort the names in the correct nested order. - * - * @param array {Array} Array of data objects - * @param index {Number} Number of times the object is nested - * @param columns {Object} Contains name formatter information - * @returns {Array} Array of labels (strings) - */ -function returnNames(array: any[], index: number, columns: any): any[] { - const names: any[] = []; - - _.forEach(array, function (obj) { - names.push({ - label: obj.name, - values: [obj.rawData], - index, - }); - - if (obj.children) { - const plusIndex = index + 1; - - _.forEach(returnNames(obj.children, plusIndex, columns), function (namedObj) { - names.push(namedObj); - }); - } - }); - - return names; -} diff --git a/src/plugins/vis_types/vislib/public/vislib/errors.ts b/src/plugins/vis_types/vislib/public/vislib/errors.ts index b394bbf6383aa..2c404e7b94cbf 100644 --- a/src/plugins/vis_types/vislib/public/vislib/errors.ts +++ b/src/plugins/vis_types/vislib/public/vislib/errors.ts @@ -33,12 +33,6 @@ export class ContainerTooSmall extends VislibError { } } -export class PieContainsAllZeros extends VislibError { - constructor() { - super('No results displayed because all values equal 0.'); - } -} - export class NoResults extends VislibError { constructor() { super( diff --git a/src/plugins/vis_types/vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.test.ts b/src/plugins/vis_types/vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.test.ts deleted file mode 100644 index 6191bf9e195d6..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.test.ts +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import type { Dimensions, Dimension } from '@kbn/vis-type-pie-plugin/public'; -import { buildHierarchicalData } from './build_hierarchical_data'; -import { Table, TableParent } from '../../types'; - -function tableVisResponseHandler(table: Table, dimensions: Dimensions) { - const converted: { - tables: Array; - } = { - tables: [], - }; - - const split = dimensions.splitColumn || dimensions.splitRow; - - if (split) { - const splitColumnIndex = split[0].accessor; - const splitColumn = table.columns[splitColumnIndex]; - const splitMap: { [key: string]: number } = {}; - let splitIndex = 0; - - table.rows.forEach((row, rowIndex) => { - const splitValue = row[splitColumn.id] as string; - - if (!splitMap.hasOwnProperty(splitValue)) { - splitMap[splitValue] = splitIndex++; - const tableGroup = { - $parent: converted, - title: `splitValue: ${splitColumn.name}`, - name: splitColumn.name, - key: splitValue, - column: splitColumnIndex, - row: rowIndex, - table, - tables: [] as Table[], - } as any; - - tableGroup.tables.push({ - $parent: tableGroup, - columns: table.columns, - rows: [], - }); - - converted.tables.push(tableGroup); - } - - const tableIndex = splitMap[splitValue]; - (converted.tables[tableIndex] as TableParent).tables![0].rows.push(row); - }); - } else { - converted.tables.push({ - columns: table.columns, - rows: table.rows, - } as Table); - } - - return converted; -} - -jest.mock('../../../services', () => ({ - getFormatService: jest.fn(() => ({ - deserialize: () => ({ - convert: jest.fn((v) => JSON.stringify(v)), - }), - })), -})); - -describe('buildHierarchicalData convertTable', () => { - describe('metric only', () => { - let dimensions: Dimensions; - let table: Table; - - beforeEach(() => { - const tabifyResponse = { - columns: [{ id: 'col-0-agg_1', name: 'Average bytes' }], - rows: [{ 'col-0-agg_1': 412032 }], - }; - dimensions = { - metric: { accessor: 0 } as Dimension, - }; - - const tableGroup = tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0] as Table; - }); - - it('should set the slices with one child to a consistent label', () => { - const results = buildHierarchicalData(table, dimensions); - const checkLabel = 'Average bytes'; - expect(results).toHaveProperty('names'); - expect(results.names).toEqual([checkLabel]); - expect(results).toHaveProperty('raw'); - expect(results.raw).toHaveProperty('rows'); - expect(results.raw.rows).toHaveLength(1); - expect(results).toHaveProperty('slices'); - expect(results.slices).toHaveProperty('children'); - expect(results.slices.children).toHaveLength(1); - expect(results.slices.children[0]).toHaveProperty('name', checkLabel); - expect(results.slices.children[0]).toHaveProperty('size', 412032); - }); - }); - - describe('threeTermBuckets', () => { - let dimensions: Dimensions; - let tables: TableParent[]; - - beforeEach(async () => { - const tabifyResponse = { - columns: [ - { id: 'col-0-agg_2', name: 'extension: Descending' }, - { id: 'col-1-agg_1', name: 'Average bytes' }, - { id: 'col-2-agg_3', name: 'geo.src: Descending' }, - { id: 'col-3-agg_1', name: 'Average bytes' }, - { id: 'col-4-agg_4', name: 'machine.os: Descending' }, - { id: 'col-5-agg_1', name: 'Average bytes' }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }; - dimensions = { - splitRow: [{ accessor: 0 } as Dimension], - metric: { accessor: 5 } as Dimension, - buckets: [{ accessor: 2 }, { accessor: 4 }] as Dimension[], - }; - const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - tables = tableGroup.tables as TableParent[]; - }); - - it('should set the correct hits attribute for each of the results', () => { - tables.forEach((t) => { - const results = buildHierarchicalData(t.tables![0], dimensions); - expect(results).toHaveProperty('hits'); - expect(results.hits).toBe(4); - }); - }); - - it('should set the correct names for each of the results', () => { - const results0 = buildHierarchicalData(tables[0].tables![0], dimensions); - expect(results0).toHaveProperty('names'); - expect(results0.names).toHaveLength(5); - - const results1 = buildHierarchicalData(tables[1].tables![0], dimensions); - expect(results1).toHaveProperty('names'); - expect(results1.names).toHaveLength(5); - - const results2 = buildHierarchicalData(tables[2].tables![0], dimensions); - expect(results2).toHaveProperty('names'); - expect(results2.names).toHaveLength(4); - }); - - it('should set the parent of the first item in the split', () => { - const results0 = buildHierarchicalData(tables[0].tables![0], dimensions); - expect(results0).toHaveProperty('slices'); - expect(results0.slices).toHaveProperty('children'); - expect(results0.slices.children).toHaveLength(2); - expect(results0.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'png'); - - const results1 = buildHierarchicalData(tables[1].tables![0], dimensions); - expect(results1).toHaveProperty('slices'); - expect(results1.slices).toHaveProperty('children'); - expect(results1.slices.children).toHaveLength(2); - expect(results1.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'css'); - - const results2 = buildHierarchicalData(tables[2].tables![0], dimensions); - expect(results2).toHaveProperty('slices'); - expect(results2.slices).toHaveProperty('children'); - expect(results2.slices.children).toHaveLength(2); - expect(results2.slices.children[0].rawData!.table.$parent).toHaveProperty('key', 'html'); - }); - }); - - describe('oneHistogramBucket', () => { - let dimensions: Dimensions; - let table: Table; - - beforeEach(async () => { - const tabifyResponse = { - columns: [ - { id: 'col-0-agg_2', name: 'bytes' }, - { id: 'col-1-1', name: 'Count' }, - ], - rows: [ - { 'col-0-agg_2': 1411862400000, 'col-1-1': 8247 }, - { 'col-0-agg_2': 1411948800000, 'col-1-1': 8184 }, - { 'col-0-agg_2': 1412035200000, 'col-1-1': 8269 }, - { 'col-0-agg_2': 1412121600000, 'col-1-1': 8141 }, - { 'col-0-agg_2': 1412208000000, 'col-1-1': 8148 }, - { 'col-0-agg_2': 1412294400000, 'col-1-1': 8219 }, - ], - }; - dimensions = { - metric: { accessor: 1 } as Dimension, - buckets: [{ accessor: 0 } as Dimension], - }; - const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0] as Table; - }); - - it('should set the hits attribute for the results', () => { - const results = buildHierarchicalData(table, dimensions); - expect(results).toHaveProperty('raw'); - expect(results).toHaveProperty('slices'); - expect(results.slices).toHaveProperty('children'); - expect(results).toHaveProperty('names'); - expect(results.names).toHaveLength(6); - }); - }); - - describe('oneRangeBucket', () => { - let dimensions: Dimensions; - let table: Table; - - beforeEach(async () => { - const tabifyResponse = { - columns: [ - { id: 'col-0-agg_2', name: 'bytes ranges' }, - { id: 'col-1-1', name: 'Count' }, - ], - rows: [ - { 'col-0-agg_2': { gte: 0, lt: 1000 }, 'col-1-1': 606 }, - { 'col-0-agg_2': { gte: 1000, lt: 2000 }, 'col-1-1': 298 }, - ], - }; - dimensions = { - metric: { accessor: 1 } as Dimension, - buckets: [{ accessor: 0, format: { id: 'range', params: { id: 'agg_2' } } } as Dimension], - }; - const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0] as Table; - }); - - it('should set the hits attribute for the results', () => { - const results = buildHierarchicalData(table, dimensions); - expect(results).toHaveProperty('raw'); - expect(results).toHaveProperty('slices'); - expect(results.slices).toHaveProperty('children'); - expect(results).toHaveProperty('names'); - expect(results.names).toHaveLength(2); - }); - }); - - describe('oneFilterBucket', () => { - let dimensions: Dimensions; - let table: Table; - - beforeEach(async () => { - const tabifyResponse = { - columns: [ - { id: 'col-0-agg_2', name: 'filters' }, - { id: 'col-1-1', name: 'Count' }, - ], - rows: [ - { 'col-0-agg_2': 'type:apache', 'col-1-1': 4844 }, - { 'col-0-agg_2': 'type:nginx', 'col-1-1': 1161 }, - ], - }; - dimensions = { - metric: { accessor: 1 } as Dimension, - buckets: [ - { - accessor: 0, - }, - ] as Dimension[], - }; - const tableGroup = await tableVisResponseHandler(tabifyResponse, dimensions); - table = tableGroup.tables[0] as Table; - }); - - it('should set the hits attribute for the results', () => { - const results = buildHierarchicalData(table, dimensions); - expect(results).toHaveProperty('raw'); - expect(results).toHaveProperty('slices'); - expect(results).toHaveProperty('names'); - expect(results.names).toHaveLength(2); - }); - }); -}); diff --git a/src/plugins/vis_types/vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.ts b/src/plugins/vis_types/vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.ts deleted file mode 100644 index da63b0b5a97a4..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/helpers/hierarchical/build_hierarchical_data.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { toArray } from 'lodash'; -import type { Dimensions } from '@kbn/vis-type-pie-plugin/public'; -import { getFormatService } from '../../../services'; -import { Table } from '../../types'; - -interface Slice { - name: string; - size: number; - parent?: Slice; - children?: []; - rawData?: { - table: Table; - row: number; - column: number; - value: string | number | object; - }; -} - -export const buildHierarchicalData = (table: Table, { metric, buckets = [] }: Dimensions) => { - let slices: Slice[]; - const names: { [key: string]: string } = {}; - const metricColumn = table.columns[metric.accessor]; - const metricFieldFormatter = metric.format; - - if (!buckets.length) { - slices = [ - { - name: metricColumn.name, - size: table.rows[0][metricColumn.id] as number, - }, - ]; - names[metricColumn.name] = metricColumn.name; - } else { - slices = []; - table.rows.forEach((row, rowIndex) => { - let parent: Slice; - let dataLevel = slices; - - buckets.forEach((bucket) => { - const bucketColumn = table.columns[bucket.accessor]; - const bucketValueColumn = table.columns[bucket.accessor + 1]; - const bucketFormatter = getFormatService().deserialize(bucket.format); - const name = bucketFormatter.convert(row[bucketColumn.id]); - const size = row[bucketValueColumn.id] as number; - names[name] = name; - - let slice = dataLevel.find((dataLevelSlice) => dataLevelSlice.name === name); - if (!slice) { - slice = { - name, - size, - parent, - children: [], - rawData: { - table, - row: rowIndex, - column: bucket.accessor, - value: row[bucketColumn.id], - }, - }; - dataLevel.push(slice); - } - - parent = slice; - dataLevel = slice.children as []; - }); - }); - } - - return { - hits: table.rows.length, - raw: table, - names: toArray(names), - tooltipFormatter: metricFieldFormatter, - slices: { - children: [...slices], - }, - }; -}; diff --git a/src/plugins/vis_types/vislib/public/vislib/helpers/index.ts b/src/plugins/vis_types/vislib/public/vislib/helpers/index.ts index f6ff2e9a69f07..87a3e583ba53c 100644 --- a/src/plugins/vis_types/vislib/public/vislib/helpers/index.ts +++ b/src/plugins/vis_types/vislib/public/vislib/helpers/index.ts @@ -7,4 +7,3 @@ */ export { buildPointSeriesData } from './point_series'; -export { buildHierarchicalData } from './hierarchical/build_hierarchical_data'; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/data.js b/src/plugins/vis_types/vislib/public/vislib/lib/data.js index fcf7916143112..27e421169a529 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/data.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/data.js @@ -49,7 +49,6 @@ export class Data { this.createColorLookupFunction = createColorLookupFunction; this.data = this.copyDataObj(data); this.type = this.getDataType(); - this._cleanVisData(); this.labels = this._getLabels(this.data); this.color = this.labels ? createColorLookupFunction(this.labels, uiState.get('vis.colors')) @@ -248,19 +247,6 @@ export class Data { ); } - /** - * Returns array of chart data objects for pie data objects - * - * @method pieData - * @returns {*} Array of chart data objects - */ - pieData() { - if (!this.data.slices) { - return this.data.rows ? this.data.rows : this.data.columns; - } - return [this.data]; - } - /** * Get attributes off the data, e.g. `tooltipFormatter` or `xAxisFormatter` * pulls the value off the first item in the array @@ -379,71 +365,6 @@ export class Data { } } - /** - * Clean visualization data from missing/wrong values. - * Currently used only to clean remove zero slices from - * pie chart. - */ - _cleanVisData() { - const visData = this.getVisData(); - if (this.type === 'slices') { - this._cleanPieChartData(visData); - } - } - - /** - * Mutate the current pie chart vis data to remove slices with - * zero values. - * @param {Array} data - */ - _cleanPieChartData(data) { - _.forEach(data, (obj) => { - obj.slices = this._removeZeroSlices(obj.slices); - }); - } - - /** - * Removes zeros from pie chart data, mutating the passed values. - * @param slices - * @returns {*} - */ - _removeZeroSlices(slices) { - if (!slices.children) { - return slices; - } - - slices = _.clone(slices); - slices.children = slices.children.reduce((children, child) => { - if (child.size !== 0) { - return [...children, this._removeZeroSlices(child)]; - } - return children; - }, []); - - return slices; - } - - /** - * Returns an array of names ordered by appearance in the nested array - * of objects - * - * @method pieNames - * @returns {Array} Array of unique names (strings) - */ - pieNames(data) { - const self = this; - const names = []; - - _.forEach(data, function (obj) { - const columns = obj.raw ? obj.raw.columns : undefined; - _.forEach(self.getNames(obj, columns), function (name) { - names.push(name); - }); - }); - - return _.uniqBy(names, 'label'); - } - /** * Inject zeros into the data * @@ -483,30 +404,12 @@ export class Data { * @returns {Function} Performs lookup on string and returns hex color */ getColorFunc() { - if (this.type === 'slices') { - return this.getPieColorFunc(); - } const defaultColors = this.uiState.get('vis.defaultColors'); const overwriteColors = this.uiState.get('vis.colors'); const colors = defaultColors ? _.defaults({}, overwriteColors, defaultColors) : overwriteColors; return this.createColorLookupFunction(this.getLabels(), colors); } - /** - * Returns a function that does color lookup on names for pie charts - * - * @method getPieColorFunc - * @returns {Function} Performs lookup on string and returns hex color - */ - getPieColorFunc() { - return this.createColorLookupFunction( - this.pieNames(this.getVisData()).map(function (d) { - return d.label; - }), - this.uiState.get('vis.colors') - ); - } - /** * ensure that the datas ordered property has a min and max * if the data represents an ordered date range. diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/data.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/data.test.js index 5a0c24bfc0ef1..ac3528381da76 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/data.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/data.test.js @@ -157,24 +157,6 @@ describe('Vislib Data Class Test Suite', function () { }); }); - describe('_removeZeroSlices', function () { - let data; - const pieData = { - slices: { - children: [{ size: 30 }, { size: 20 }, { size: 0 }], - }, - }; - - beforeEach(function () { - data = new Data(pieData, mockUiState, () => undefined); - }); - - it('should remove zero values', function () { - const slices = data._removeZeroSlices(data.data.slices); - expect(slices.children.length).toBe(2); - }); - }); - describe('Data.flatten', function () { let serIn; let serOut; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.js b/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.js index e93ed922b3fd1..0a74a286c34eb 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.js @@ -123,23 +123,6 @@ export class Dispatch { return reduce(this._listeners, (count, handlers) => count + size(handlers), 0); } - _pieClickResponse(data) { - const points = []; - - let dataPointer = data; - while (dataPointer && dataPointer.rawData) { - points.push(dataPointer.rawData); - dataPointer = dataPointer.parent; - } - - if (get(data, 'rawData.table.$parent')) { - const { table, column, row, key } = get(data, 'rawData.table.$parent'); - points.push({ table, column, row, value: key }); - } - - return points; - } - _seriesClickResponse(data) { const points = []; @@ -170,7 +153,7 @@ export class Dispatch { const data = d.input || d; return { - data: isSlices ? this._pieClickResponse(data) : this._seriesClickResponse(data), + data: this._seriesClickResponse(data), }; } diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js index a500aa00e6d21..93b691242a302 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js @@ -179,27 +179,6 @@ describe('Vislib Dispatch Class Test Suite', function () { }); describe('clickEvent handler', () => { - describe('for pie chart', () => { - test('prepares data points', () => { - const expectedResponse = [{ column: 0, row: 0, table: {}, value: 0 }]; - const d = { rawData: { column: 0, row: 0, table: {}, value: 0 } }; - const chart = _.first(vis.handler.charts); - const response = chart.events.clickEventResponse(d, { isSlices: true }); - expect(response.data).toEqual(expectedResponse); - }); - - test('remove invalid points', () => { - const expectedResponse = [{ column: 0, row: 0, table: {}, value: 0 }]; - const d = { - rawData: { column: 0, row: 0, table: {}, value: 0 }, - yRaw: { table: {}, value: 0 }, - }; - const chart = _.first(vis.handler.charts); - const response = chart.events.clickEventResponse(d, { isSlices: true }); - expect(response.data).toEqual(expectedResponse); - }); - }); - describe('for xy charts', () => { test('prepares data points', () => { const expectedResponse = [{ column: 0, row: 0, table: {}, value: 0 }]; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout_types.js b/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout_types.js index 2aa6fc8010fa9..400e0c36467c3 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout_types.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout_types.js @@ -7,11 +7,9 @@ */ import { columnLayout } from './types/column_layout'; -import { pieLayout } from './types/pie_layout'; import { gaugeLayout } from './types/gauge_layout'; export const layoutTypes = { - pie: pieLayout, gauge: gaugeLayout, goal: gaugeLayout, metric: gaugeLayout, diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/layout/types/pie_layout.js b/src/plugins/vis_types/vislib/public/vislib/lib/layout/types/pie_layout.js deleted file mode 100644 index a09fe8865c70b..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/lib/layout/types/pie_layout.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { chartSplit } from '../splits/pie_chart/chart_split'; -import { chartTitleSplit } from '../splits/pie_chart/chart_title_split'; - -/** - * Specifies the visualization layout for column charts. - * - * This is done using an array of objects. The first object has - * a `parent` DOM element, a DOM `type` (e.g. div, svg, etc), - * and a `class` (required). Each child can omit the parent object, - * but must include a type and class. - * - * Optionally, you can specify `datum` to be bound to the DOM - * element, a `splits` function that divides the selected element - * into more DOM elements based on a callback function provided, or - * a children array which nests other layout objects. - * - * Objects in children arrays are children of the current object and return - * DOM elements which are children of their respective parent element. - */ - -export function pieLayout(el, data) { - if (!el || !data) { - throw new Error('Both an el and data need to be specified'); - } - - return [ - { - parent: el, - type: 'div', - class: 'visWrapper', - datum: data, - children: [ - { - type: 'div', - class: 'visAxis__splitTitles--y', - splits: chartTitleSplit, - }, - { - type: 'div', - class: 'visWrapper__column', - children: [ - { - type: 'div', - class: 'visWrapper__chart', - splits: chartSplit, - }, - { - type: 'div', - class: 'visAxis__splitTitles--x', - splits: chartTitleSplit, - }, - ], - }, - ], - }, - ]; -} diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/types/index.js b/src/plugins/vis_types/vislib/public/vislib/lib/types/index.js index 4a9dd0bd512ca..6fb0391482e54 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/types/index.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/types/index.js @@ -7,11 +7,9 @@ */ import { vislibPointSeriesTypes as pointSeries } from './point_series'; -import { vislibPieConfig } from './pie'; import { vislibGaugeConfig } from './gauge'; export const vislibTypesConfig = { - pie: vislibPieConfig, heatmap: pointSeries.heatmap, gauge: vislibGaugeConfig, goal: vislibGaugeConfig, diff --git a/src/plugins/vis_types/vislib/public/vislib/response_handler.js b/src/plugins/vis_types/vislib/public/vislib/response_handler.js index 996037cb8f5cd..cba5cdc5c5b70 100644 --- a/src/plugins/vis_types/vislib/public/vislib/response_handler.js +++ b/src/plugins/vis_types/vislib/public/vislib/response_handler.js @@ -7,7 +7,7 @@ */ import { getFormatService } from '../services'; -import { buildHierarchicalData, buildPointSeriesData } from './helpers'; +import { buildPointSeriesData } from './helpers'; function tableResponseHandler(table, dimensions) { const converted = { tables: [] }; @@ -112,5 +112,3 @@ function handlerFunction(convertTable) { } export const vislibSeriesResponseHandler = handlerFunction(buildPointSeriesData); - -export const vislibSlicesResponseHandler = handlerFunction(buildHierarchicalData); diff --git a/src/plugins/vis_types/vislib/public/vislib/response_handler.test.ts b/src/plugins/vis_types/vislib/public/vislib/response_handler.test.ts index 547d293efdea8..d342d2542cd5b 100644 --- a/src/plugins/vis_types/vislib/public/vislib/response_handler.test.ts +++ b/src/plugins/vis_types/vislib/public/vislib/response_handler.test.ts @@ -9,41 +9,15 @@ import { setFormatService } from '../services'; jest.mock('./helpers', () => ({ - buildHierarchicalData: jest.fn(() => ({})), buildPointSeriesData: jest.fn(() => ({})), })); // @ts-ignore -import { vislibSeriesResponseHandler, vislibSlicesResponseHandler } from './response_handler'; -import { buildHierarchicalData, buildPointSeriesData } from './helpers'; -import { Table } from './types'; +import { vislibSeriesResponseHandler } from './response_handler'; +import { buildPointSeriesData } from './helpers'; describe('response_handler', () => { - describe('vislibSlicesResponseHandler', () => { - test('should not call buildHierarchicalData when no columns', () => { - vislibSlicesResponseHandler({ rows: [] }, {}); - expect(buildHierarchicalData).not.toHaveBeenCalled(); - }); - - test('should call buildHierarchicalData', () => { - const response = { - rows: [{ 'col-0-1': 1 }], - columns: [{ id: 'col-0-1', name: 'Count' }], - }; - const dimensions = { metric: { accessor: 0 } }; - vislibSlicesResponseHandler(response, dimensions); - - expect(buildHierarchicalData).toHaveBeenCalledWith( - { columns: [...response.columns], rows: [...response.rows] }, - dimensions - ); - }); - }); - describe('vislibSeriesResponseHandler', () => { - let resp: Table; - let expected: any; - beforeAll(() => { setFormatService({ deserialize: () => ({ @@ -52,27 +26,6 @@ describe('response_handler', () => { } as any); }); - beforeAll(() => { - resp = { - rows: [ - { 'col-0-3': 158599872, 'col-1-1': 1 }, - { 'col-0-3': 158599893, 'col-1-1': 2 }, - { 'col-0-3': 158599908, 'col-1-1': 1 }, - ], - columns: [ - { id: 'col-0-3', name: 'timestamp per 30 seconds' }, - { id: 'col-1-1', name: 'Count' }, - ], - } as Table; - - const colId = resp.columns[0].id; - expected = [ - { label: `${resp.rows[0][colId]}: ${resp.columns[0].name}` }, - { label: `${resp.rows[1][colId]}: ${resp.columns[0].name}` }, - { label: `${resp.rows[2][colId]}: ${resp.columns[0].name}` }, - ]; - }); - test('should not call buildPointSeriesData when no columns', () => { vislibSeriesResponseHandler({ rows: [] }, {}); expect(buildPointSeriesData).not.toHaveBeenCalled(); @@ -91,29 +44,5 @@ describe('response_handler', () => { dimensions ); }); - - test('should split columns', () => { - const dimensions = { - x: null, - y: [{ accessor: 1 }], - splitColumn: [{ accessor: 0 }], - }; - - const convertedResp = vislibSlicesResponseHandler(resp, dimensions); - expect(convertedResp.columns).toHaveLength(resp.rows.length); - expect(convertedResp.columns).toEqual(expected); - }); - - test('should split rows', () => { - const dimensions = { - x: null, - y: [{ accessor: 1 }], - splitRow: [{ accessor: 0 }], - }; - - const convertedResp = vislibSlicesResponseHandler(resp, dimensions); - expect(convertedResp.rows).toHaveLength(resp.rows.length); - expect(convertedResp.rows).toEqual(expected); - }); }); }); diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/_chart.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/_chart.js index 281415503349f..f572a388e5cf1 100644 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/_chart.js +++ b/src/plugins/vis_types/vislib/public/vislib/visualizations/_chart.js @@ -12,11 +12,7 @@ import _ from 'lodash'; import { dataLabel } from '../lib/_data_label'; import { Dispatch } from '../lib/dispatch'; import { getFormatService } from '../../services'; -import { - Tooltip, - hierarchicalTooltipFormatter, - pointSeriesTooltipFormatter, -} from '../components/tooltip'; +import { Tooltip, pointSeriesTooltipFormatter } from '../components/tooltip'; /** * The Base Class for all visualizations. @@ -39,10 +35,7 @@ export class Chart { const fieldFormatter = getFormatService().deserialize( this.handler.data.get('tooltipFormatter') ); - const tooltipFormatterProvider = - this.handler.visConfig.get('type') === 'pie' - ? hierarchicalTooltipFormatter - : pointSeriesTooltipFormatter; + const tooltipFormatterProvider = pointSeriesTooltipFormatter; const tooltipFormatter = tooltipFormatterProvider(fieldFormatter); if (this.handler.visConfig && this.handler.visConfig.get('addTooltip', false)) { diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart.js deleted file mode 100644 index 447594a422cd6..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart.js +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import d3 from 'd3'; -import _ from 'lodash'; -import $ from 'jquery'; -import numeral from '@elastic/numeral'; -import { PieContainsAllZeros, ContainerTooSmall } from '../errors'; -import { Chart } from './_chart'; -import { truncateLabel } from '../components/labels/truncate_labels'; - -const defaults = { - isDonut: false, - showTooltip: true, - color: undefined, - fillColor: undefined, -}; -/** - * Pie Chart Visualization - * - * @class PieChart - * @constructor - * @extends Chart - * @param handler {Object} Reference to the Handler Class Constructor - * @param el {HTMLElement} HTML element to which the chart will be appended - * @param chartData {Object} Elasticsearch query results for this specific chart - */ -export class PieChart extends Chart { - constructor(handler, chartEl, chartData, uiSettings) { - super(handler, chartEl, chartData, uiSettings); - const charts = this.handler.data.getVisData(); - this._validatePieData(charts); - this._attr = _.defaults(handler.visConfig.get('chart', {}), defaults); - } - - /** - * Checks whether pie slices have all zero values. - * If so, an error is thrown. - */ - _validatePieData(charts) { - const isAllZeros = charts.every((chart) => { - return chart.slices.children.length === 0; - }); - - if (isAllZeros) { - throw new PieContainsAllZeros(); - } - } - - /** - * Adds Events to SVG paths - * - * @method addPathEvents - * @param element {D3.Selection} Reference to SVG path - * @returns {D3.Selection} SVG path with event listeners attached - */ - addPathEvents(element) { - const events = this.events; - - return element - .call(events.addHoverEvent()) - .call(events.addMouseoutEvent()) - .call(events.addClickEvent()); - } - - convertToPercentage(slices) { - (function assignPercentages(slices) { - if (slices.sumOfChildren != null) return; - - const parent = slices; - const children = parent.children; - const parentPercent = parent.percentOfParent; - - const sum = (parent.sumOfChildren = Math.abs( - children.reduce(function (sum, child) { - return sum + Math.abs(child.size); - }, 0) - )); - - children.forEach(function (child) { - child.percentOfGroup = Math.abs(child.size) / sum; - child.percentOfParent = child.percentOfGroup; - - if (parentPercent != null) { - child.percentOfParent *= parentPercent; - } - - if (child.children) { - assignPercentages(child); - } - }); - })(slices); - } - - /** - * Adds pie paths to SVG - * - * @method addPath - * @param width {Number} Width of SVG - * @param height {Number} Height of SVG - * @param svg {HTMLElement} Chart SVG - * @param slices {Object} Chart data - * @returns {D3.Selection} SVG with paths attached - */ - addPath(width, height, svg, slices) { - const self = this; - const marginFactor = 0.95; - const isDonut = self._attr.isDonut; - const radius = (Math.min(width, height) / 2) * marginFactor; - const color = self.handler.data.getPieColorFunc(); - const tooltip = self.tooltip; - const isTooltip = self._attr.addTooltip; - - const arcs = svg.append('g').attr('class', 'arcs'); - const labels = svg.append('g').attr('class', 'labels'); - - const showLabels = self._attr.labels.show; - const showValues = self._attr.labels.values; - const truncateLabelLength = self._attr.labels.truncate; - const showOnlyOnLastLevel = self._attr.labels.last_level; - - const partition = d3.layout - .partition() - .sort(null) - .value(function (d) { - return d.percentOfParent * 100; - }); - - const x = d3.scale.linear().range([0, 2 * Math.PI]); - const y = d3.scale.sqrt().range([0, showLabels ? radius * 0.7 : radius]); - - const startAngle = function (d) { - return Math.max(0, Math.min(2 * Math.PI, x(d.x))); - }; - - const endAngle = function (d) { - if (d.dx < 1e-8) return x(d.x); - return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); - }; - - const arc = d3.svg - .arc() - .startAngle(startAngle) - .endAngle(endAngle) - .innerRadius(function (d) { - // option for a single layer, i.e pie chart - if (d.depth === 1 && !isDonut) { - // return no inner radius - return 0; - } - - return Math.max(0, y(d.y)); - }) - .outerRadius(function (d) { - return Math.max(0, y(d.y + d.dy)); - }); - - const outerArc = d3.svg - .arc() - .startAngle(startAngle) - .endAngle(endAngle) - .innerRadius(radius * 0.8) - .outerRadius(radius * 0.8); - - let maxDepth = 0; - const path = arcs - .datum(slices) - .selectAll('path') - .data(partition.nodes) - .enter() - .append('path') - .attr('d', arc) - .attr('class', function (d) { - if (d.depth === 0) { - return; - } - if (d.depth > maxDepth) maxDepth = d.depth; - return 'slice'; - }) - .attr('data-test-subj', function (d) { - if (d.name) { - return `pieSlice-${d.name.split(' ').join('-')}`; - } - }) - .call(self._addIdentifier, 'name') - .style('fill', function (d) { - if (d.depth === 0) { - return 'none'; - } - return color(d.name); - }); - - // add labels - if (showLabels) { - const labelGroups = labels.datum(slices).selectAll('.label').data(partition.nodes); - - // create an empty quadtree to hold label positions - const svgParentNode = svg.node().parentNode.parentNode; - const svgBBox = { - width: svgParentNode.clientWidth, - height: svgParentNode.clientHeight, - }; - - const labelLayout = d3.geom - .quadtree() - .extent([ - [-svgBBox.width, -svgBBox.height], - [svgBBox.width, svgBBox.height], - ]) - .x(function (d) { - return d.position.x; - }) - .y(function (d) { - return d.position.y; - })([]); - - labelGroups - .enter() - .append('g') - .attr('class', 'label') - .append('text') - .text(function (d) { - if (d.depth === 0) { - d3.select(this.parentNode).remove(); - return; - } - if (showValues) { - const value = numeral(d.value / 100).format('0.[00]%'); - return `${d.name} (${value})`; - } - return d.name; - }) - .text(function () { - return truncateLabel(this, truncateLabelLength); - }) - .attr('text-anchor', function (d) { - const midAngle = startAngle(d) + (endAngle(d) - startAngle(d)) / 2; - return midAngle < Math.PI ? 'start' : 'end'; - }) - .attr('class', 'label-text') - .each(function resolveConflicts(d) { - if (d.depth === 0) return; - - const parentNode = this.parentNode; - if (showOnlyOnLastLevel && maxDepth !== d.depth) { - d3.select(parentNode).remove(); - return; - } - - const bbox = this.getBBox(); - const pos = outerArc.centroid(d); - const midAngle = startAngle(d) + (endAngle(d) - startAngle(d)) / 2; - pos[1] += 4; - pos[0] = (0.7 + d.depth / 10) * radius * (midAngle < Math.PI ? 1 : -1); - d.position = { - x: pos[0], - y: pos[1], - left: midAngle < Math.PI ? pos[0] : pos[0] - bbox.width, - right: midAngle > Math.PI ? pos[0] + bbox.width : pos[0], - bottom: pos[1] + 5, - top: pos[1] - bbox.height - 5, - }; - - const conflicts = []; - labelLayout.visit(function (node) { - if (!node.point) return; - if (conflicts.length) return true; - - const point = node.point.position; - const current = d.position; - if (point) { - const horizontalConflict = - (point.left < 0 && current.left < 0) || (point.left > 0 && current.left > 0); - const verticalConflict = - (point.top >= current.top && point.top <= current.bottom) || - (point.top <= current.top && point.bottom >= current.top); - - if (horizontalConflict && verticalConflict) { - point.point = node.point; - conflicts.push(point); - } - - return true; - } - }); - - if (conflicts.length) { - d3.select(parentNode).remove(); - return; - } - - labelLayout.add(d); - }) - .attr('x', function (d) { - if (d.depth === 0 || !d.position) { - return; - } - return d.position.x; - }) - .attr('y', function (d) { - if (d.depth === 0 || !d.position) { - return; - } - return d.position.y; - }); - - labelGroups - .append('polyline') - .attr('points', function (d) { - if (d.depth === 0 || !d.position) { - return; - } - const pos1 = outerArc.centroid(d); - const x2 = d.position.x > 0 ? d.position.x - 10 : d.position.x + 10; - const pos2 = [x2, d.position.y - 4]; - pos1[1] = pos2[1]; - return [arc.centroid(d), pos1, pos2]; - }) - .attr('class', 'label-line'); - } - - if (isTooltip) { - path.call(tooltip.render()); - } - - return path; - } - - _validateContainerSize(width, height) { - const minWidth = 20; - const minHeight = 20; - - if (width <= minWidth || height <= minHeight) { - throw new ContainerTooSmall(); - } - } - - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the pie chart - */ - draw() { - const self = this; - - return function (selection) { - selection.each(function (data) { - const slices = data.slices; - const div = d3.select(this); - const width = $(this).width(); - const height = $(this).height(); - - if (!slices.children.length) return; - - self.convertToPercentage(slices); - self._validateContainerSize(width, height); - - const svg = div - .append('svg') - .attr('width', width) - .attr('height', height) - .attr('focusable', 'false') - .append('g') - .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); - - const path = self.addPath(width, height, svg, slices); - self.addPathEvents(path); - - self.events.emit('rendered', { - chart: data, - }); - - return svg; - }); - }; - } -} diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart.test.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart.test.js deleted file mode 100644 index 6705875ce1405..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart.test.js +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import d3 from 'd3'; -import _ from 'lodash'; -import $ from 'jquery'; -import { - setHTMLElementClientSizes, - setSVGElementGetBBox, - setSVGElementGetComputedTextLength, -} from '@kbn/test-jest-helpers'; -import { getMockUiState } from '../../fixtures/mocks'; -import { getVis } from './_vis_fixture'; -import { pieChartMockData } from './pie_chart_mock_data'; - -const names = ['rows', 'columns', 'slices']; - -const sizes = [0, 5, 15, 30, 60, 120]; - -let mockedHTMLElementClientSizes = {}; -let mockWidth; -let mockHeight; -let mockedSVGElementGetBBox; -let mockedSVGElementGetComputedTextLength; - -describe('No global chart settings', function () { - const vislibParams1 = { - el: '
', - type: 'pie', - legendDisplay: 'show', - addTooltip: true, - }; - let chart1; - let mockUiState; - - beforeAll(() => { - mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); - mockedSVGElementGetBBox = setSVGElementGetBBox(100); - mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); - mockWidth = jest.spyOn($.prototype, 'width').mockReturnValue(120); - mockHeight = jest.spyOn($.prototype, 'height').mockReturnValue(120); - }); - - beforeEach(() => { - chart1 = getVis(vislibParams1); - mockUiState = getMockUiState(); - }); - - beforeEach(async () => { - chart1.render(pieChartMockData.rowData, mockUiState); - }); - - afterEach(function () { - chart1.destroy(); - }); - - afterAll(() => { - mockedHTMLElementClientSizes.mockRestore(); - mockedSVGElementGetBBox.mockRestore(); - mockedSVGElementGetComputedTextLength.mockRestore(); - mockWidth.mockRestore(); - mockHeight.mockRestore(); - }); - - test('should render chart titles for all charts', function () { - expect($(chart1.element).find('.visAxis__splitTitles--y').length).toBe(1); - }); - - describe('_validatePieData method', function () { - const allZeros = [ - { slices: { children: [] } }, - { slices: { children: [] } }, - { slices: { children: [] } }, - ]; - - const someZeros = [ - { slices: { children: [{}] } }, - { slices: { children: [{}] } }, - { slices: { children: [] } }, - ]; - - const noZeros = [ - { slices: { children: [{}] } }, - { slices: { children: [{}] } }, - { slices: { children: [{}] } }, - ]; - - test('should throw an error when all charts contain zeros', function () { - expect(function () { - chart1.handler.ChartClass.prototype._validatePieData(allZeros); - }).toThrowError(); - }); - - test('should not throw an error when only some or no charts contain zeros', function () { - expect(function () { - chart1.handler.ChartClass.prototype._validatePieData(someZeros); - }).not.toThrowError(); - expect(function () { - chart1.handler.ChartClass.prototype._validatePieData(noZeros); - }).not.toThrowError(); - }); - }); -}); - -describe('Vislib PieChart Class Test Suite', function () { - beforeAll(() => { - mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); - mockedSVGElementGetBBox = setSVGElementGetBBox(100); - mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); - let width = 120; - let height = 120; - const mockWidth = jest.spyOn($.prototype, 'width'); - mockWidth.mockImplementation((size) => { - if (size === undefined) { - return width; - } - width = size; - }); - const mockHeight = jest.spyOn($.prototype, 'height'); - mockHeight.mockImplementation((size) => { - if (size === undefined) { - return height; - } - height = size; - }); - }); - - afterAll(() => { - mockedHTMLElementClientSizes.mockRestore(); - mockedSVGElementGetBBox.mockRestore(); - mockedSVGElementGetComputedTextLength.mockRestore(); - mockWidth.mockRestore(); - mockHeight.mockRestore(); - }); - - ['rowData', 'columnData', 'sliceData'].forEach(function (aggItem, i) { - describe('Vislib PieChart Class Test Suite for ' + names[i] + ' data', function () { - const mockPieData = pieChartMockData[aggItem]; - - const vislibParams = { - type: 'pie', - legendDisplay: 'show', - addTooltip: true, - }; - let vis; - - beforeEach(async () => { - vis = getVis(vislibParams); - const mockUiState = getMockUiState(); - vis.render(mockPieData, mockUiState); - }); - - afterEach(function () { - vis.destroy(); - }); - - describe('addPathEvents method', function () { - let path; - let d3selectedPath; - let onClick; - let onMouseOver; - - beforeEach(function () { - vis.handler.charts.forEach(function (chart) { - path = $(chart.chartEl).find('path')[0]; - d3selectedPath = d3.select(path)[0][0]; - - // d3 instance of click and hover - onClick = !!d3selectedPath.__onclick; - onMouseOver = !!d3selectedPath.__onmouseover; - }); - }); - - test('should attach a click event', function () { - vis.handler.charts.forEach(function () { - expect(onClick).toBe(true); - }); - }); - - test('should attach a hover event', function () { - vis.handler.charts.forEach(function () { - expect(onMouseOver).toBe(true); - }); - }); - }); - - describe('addPath method', function () { - let width; - let height; - let svg; - let slices; - - test('should return an SVG object', function () { - vis.handler.charts.forEach(function (chart) { - $(chart.chartEl).find('svg').empty(); - width = $(chart.chartEl).width(); - height = $(chart.chartEl).height(); - svg = d3.select($(chart.chartEl).find('svg')[0]); - slices = chart.chartData.slices; - expect(_.isObject(chart.addPath(width, height, svg, slices))).toBe(true); - }); - }); - - test('should draw path elements', function () { - vis.handler.charts.forEach(function (chart) { - // test whether path elements are drawn - expect($(chart.chartEl).find('path').length).toBeGreaterThan(0); - }); - }); - - test('should draw labels', function () { - vis.handler.charts.forEach(function (chart) { - $(chart.chartEl).find('svg').empty(); - width = $(chart.chartEl).width(); - height = $(chart.chartEl).height(); - svg = d3.select($(chart.chartEl).find('svg')[0]); - slices = chart.chartData.slices; - chart._attr.labels.show = true; - chart.addPath(width, height, svg, slices); - expect($(chart.chartEl).find('text.label-text').length).toBeGreaterThan(0); - }); - }); - }); - - describe('draw method', function () { - test('should return a function', function () { - vis.handler.charts.forEach(function (chart) { - expect(_.isFunction(chart.draw())).toBe(true); - }); - }); - }); - - sizes.forEach(function (size) { - describe('containerTooSmall error', function () { - test('should throw an error', function () { - // 20px is the minimum height and width - vis.handler.charts.forEach(function (chart) { - $(chart.chartEl).height(size); - $(chart.chartEl).width(size); - - if (size < 20) { - expect(function () { - chart.render(); - }).toThrowError(); - } - }); - }); - - test('should not throw an error', function () { - vis.handler.charts.forEach(function (chart) { - $(chart.chartEl).height(size); - $(chart.chartEl).width(size); - - if (size > 20) { - expect(function () { - chart.render(); - }).not.toThrowError(); - } - }); - }); - }); - }); - }); - }); -}); diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart_mock_data.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart_mock_data.js deleted file mode 100644 index 92126a91f7bf3..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart_mock_data.js +++ /dev/null @@ -1,3731 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export const pieChartMockData = { - rowData: { - rows: [ - { - hits: 4, - raw: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - names: ['IT', 'win', 'mac', 'US', 'linux'], - slices: { - children: [ - { - name: 'IT', - size: 9299, - children: [ - { - name: 'win', - size: 0, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 4, - value: 'win', - }, - }, - { - name: 'mac', - size: 9299, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 1, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 2, - value: 'IT', - }, - }, - { - name: 'US', - size: 8293, - children: [ - { - name: 'linux', - size: 3992, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 4, - value: 'linux', - }, - }, - { - name: 'mac', - size: 3029, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 3, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 2, - value: 'US', - }, - }, - ], - }, - label: 'png: extension: Descending', - }, - { - hits: 4, - raw: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - names: ['MX', 'win', 'mac', 'US', 'linux'], - slices: { - children: [ - { - name: 'MX', - size: 9299, - children: [ - { - name: 'win', - size: 4992, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 4, - value: 'win', - }, - }, - { - name: 'mac', - size: 5892, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 1, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 2, - value: 'MX', - }, - }, - { - name: 'US', - size: 8293, - children: [ - { - name: 'linux', - size: 3992, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 4, - value: 'linux', - }, - }, - { - name: 'mac', - size: 3029, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 3, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 2, - value: 'US', - }, - }, - ], - }, - label: 'css: extension: Descending', - }, - { - hits: 4, - raw: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - names: ['CN', 'win', 'mac', 'FR'], - slices: { - children: [ - { - name: 'CN', - size: 9299, - children: [ - { - name: 'win', - size: 4992, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 4, - value: 'win', - }, - }, - { - name: 'mac', - size: 5892, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 1, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 2, - value: 'CN', - }, - }, - { - name: 'FR', - size: 8293, - children: [ - { - name: 'win', - size: 3992, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 4, - value: 'win', - }, - }, - { - name: 'mac', - size: 3029, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 3, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 2, - value: 'FR', - }, - }, - ], - }, - label: 'html: extension: Descending', - }, - ], - hits: 12, - }, - columnData: { - columns: [ - { - hits: 4, - raw: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - names: ['IT', 'win', 'mac', 'US', 'linux'], - slices: { - children: [ - { - name: 'IT', - size: 9299, - children: [ - { - name: 'win', - size: 0, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 4, - value: 'win', - }, - }, - { - name: 'mac', - size: 9299, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 1, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 2, - value: 'IT', - }, - }, - { - name: 'US', - size: 8293, - children: [ - { - name: 'linux', - size: 3992, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 4, - value: 'linux', - }, - }, - { - name: 'mac', - size: 3029, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 3, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 0, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 2, - value: 'US', - }, - }, - ], - }, - label: 'png: extension: Descending', - }, - { - hits: 4, - raw: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - names: ['MX', 'win', 'mac', 'US', 'linux'], - slices: { - children: [ - { - name: 'MX', - size: 9299, - children: [ - { - name: 'win', - size: 4992, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 4, - value: 'win', - }, - }, - { - name: 'mac', - size: 5892, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 1, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 2, - value: 'MX', - }, - }, - { - name: 'US', - size: 8293, - children: [ - { - name: 'linux', - size: 3992, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 4, - value: 'linux', - }, - }, - { - name: 'mac', - size: 3029, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 3, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'linux', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 2, - value: 'US', - }, - }, - ], - }, - label: 'css: extension: Descending', - }, - { - hits: 4, - raw: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - names: ['CN', 'win', 'mac', 'FR'], - slices: { - children: [ - { - name: 'CN', - size: 9299, - children: [ - { - name: 'win', - size: 4992, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 4, - value: 'win', - }, - }, - { - name: 'mac', - size: 5892, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 1, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 0, - column: 2, - value: 'CN', - }, - }, - { - name: 'FR', - size: 8293, - children: [ - { - name: 'win', - size: 3992, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 4, - value: 'win', - }, - }, - { - name: 'mac', - size: 3029, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 3, - column: 4, - value: 'mac', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'extension: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'machine.os: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - { - id: 'col-4-agg_4', - name: 'geo.src: Descending', - }, - { - id: 'col-5-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 4992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - 'col-5-agg_1': 5892, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'win', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3992, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-4-agg_4': 'mac', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - 'col-5-agg_1': 3029, - }, - ], - }, - row: 2, - column: 2, - value: 'FR', - }, - }, - ], - }, - label: 'html: extension: Descending', - }, - ], - hits: 12, - }, - sliceData: { - hits: 6, - raw: { - columns: [ - { - id: 'col-0-agg_2', - name: 'machine.os: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - ], - }, - names: ['png', 'IT', 'US', 'css', 'MX', 'html', 'CN', 'FR'], - slices: { - children: [ - { - name: 'png', - size: 412032, - children: [ - { - name: 'IT', - size: 9299, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'machine.os: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - ], - }, - row: 0, - column: 2, - value: 'IT', - }, - }, - { - name: 'US', - size: 8293, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'machine.os: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - ], - }, - row: 1, - column: 2, - value: 'US', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'machine.os: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - ], - }, - row: 0, - column: 0, - value: 'png', - }, - }, - { - name: 'css', - size: 412032, - children: [ - { - name: 'MX', - size: 9299, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'machine.os: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - ], - }, - row: 2, - column: 2, - value: 'MX', - }, - }, - { - name: 'US', - size: 8293, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'machine.os: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - ], - }, - row: 3, - column: 2, - value: 'US', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'machine.os: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - ], - }, - row: 2, - column: 0, - value: 'css', - }, - }, - { - name: 'html', - size: 412032, - children: [ - { - name: 'CN', - size: 9299, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'machine.os: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - ], - }, - row: 4, - column: 2, - value: 'CN', - }, - }, - { - name: 'FR', - size: 8293, - children: [], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'machine.os: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - ], - }, - row: 5, - column: 2, - value: 'FR', - }, - }, - ], - rawData: { - table: { - columns: [ - { - id: 'col-0-agg_2', - name: 'machine.os: Descending', - }, - { - id: 'col-1-agg_1', - name: 'Average bytes', - }, - { - id: 'col-2-agg_3', - name: 'geo.src: Descending', - }, - { - id: 'col-3-agg_1', - name: 'Average bytes', - }, - ], - rows: [ - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'IT', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'png', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'MX', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'css', - 'col-2-agg_3': 'US', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'CN', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 9299, - }, - { - 'col-0-agg_2': 'html', - 'col-2-agg_3': 'FR', - 'col-1-agg_1': 412032, - 'col-3-agg_1': 8293, - }, - ], - }, - row: 4, - column: 0, - value: 'html', - }, - }, - ], - }, - }, -}; diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/vis_types.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/vis_types.js index 80c83af1ab802..c442bb7d9f7e0 100644 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/vis_types.js +++ b/src/plugins/vis_types/vislib/public/vislib/visualizations/vis_types.js @@ -7,11 +7,9 @@ */ import { PointSeries } from './point_series'; -import { PieChart } from './pie_chart'; import { GaugeChart } from './gauge_chart'; export const visTypes = { - pie: PieChart, point_series: PointSeries, gauge: GaugeChart, goal: GaugeChart, diff --git a/src/plugins/vis_types/vislib/tsconfig.json b/src/plugins/vis_types/vislib/tsconfig.json index 0ff4d8d2900e4..c63a51db242b9 100644 --- a/src/plugins/vis_types/vislib/tsconfig.json +++ b/src/plugins/vis_types/vislib/tsconfig.json @@ -22,7 +22,6 @@ { "path": "../../kibana_utils/tsconfig.json" }, { "path": "../../vis_types/gauge/tsconfig.json" }, { "path": "../../vis_types/xy/tsconfig.json" }, - { "path": "../../vis_types/pie/tsconfig.json" }, { "path": "../../vis_types/heatmap/tsconfig.json" }, ] } diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.test.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.test.tsx index 58727d9118c5b..b60a0ae931872 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.test.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.test.tsx @@ -43,7 +43,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => ({ jest.mock('../../services', () => ({ getUISettings: jest.fn(() => ({ - get: jest.fn((token) => Boolean(token === 'visualization:visualize:legacyPieChartsLibrary')), + get: jest.fn(), })), })); @@ -201,45 +201,4 @@ describe('VisualizeEditorCommon', () => { ); expect(wrapper.find(VizChartWarning).length).toBe(0); }); - - it('should display a warning callout for old pie implementation', async () => { - const wrapper = shallowWithIntl( - {}} - hasUnappliedChanges={false} - isEmbeddableRendered={false} - onAppLeave={() => {}} - visEditorRef={React.createRef()} - visInstance={ - { - savedVis: { - id: 'test', - sharingSavedObjectProps: { - outcome: 'conflict', - aliasTargetId: 'alias_id', - }, - }, - vis: { - type: { - title: 'pie', - name: 'pie', - }, - data: { - aggs: { - aggs: [ - { - schema: 'buckets', - }, - ], - }, - }, - }, - } as unknown as VisualizeEditorVisInstance - } - /> - ); - expect(wrapper.find(VizChartWarning).length).toBe(1); - }); }); diff --git a/src/plugins/visualizations/public/visualize_app/constants.ts b/src/plugins/visualizations/public/visualize_app/constants.ts index 36f1844d998b2..fd256cb5bbb86 100644 --- a/src/plugins/visualizations/public/visualize_app/constants.ts +++ b/src/plugins/visualizations/public/visualize_app/constants.ts @@ -8,4 +8,3 @@ export const NEW_HEATMAP_CHARTS_LIBRARY = 'visualization:visualize:legacyHeatmapChartsLibrary'; export const NEW_GAUGE_CHARTS_LIBRARY = 'visualization:visualize:legacyGaugeChartsLibrary'; -export const NEW_PIE_CHARTS_LIBRARY = 'visualization:visualize:legacyPieChartsLibrary'; diff --git a/src/plugins/visualizations/public/visualize_app/utils/split_chart_warning_helpers.ts b/src/plugins/visualizations/public/visualize_app/utils/split_chart_warning_helpers.ts index 5ff9bc3d93067..5efbe7494ef59 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/split_chart_warning_helpers.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/split_chart_warning_helpers.ts @@ -8,11 +8,7 @@ import { $Values } from '@kbn/utility-types'; import { AggConfigs } from '@kbn/data-plugin/common'; -import { - NEW_HEATMAP_CHARTS_LIBRARY, - NEW_GAUGE_CHARTS_LIBRARY, - NEW_PIE_CHARTS_LIBRARY, -} from '../constants'; +import { NEW_HEATMAP_CHARTS_LIBRARY, NEW_GAUGE_CHARTS_LIBRARY } from '../constants'; export const CHARTS_WITHOUT_SMALL_MULTIPLES = { heatmap: 'heatmap', @@ -30,7 +26,6 @@ export type CHARTS_TO_BE_DEPRECATED = $Values; export const CHARTS_CONFIG_TOKENS = { [CHARTS_WITHOUT_SMALL_MULTIPLES.heatmap]: NEW_HEATMAP_CHARTS_LIBRARY, [CHARTS_WITHOUT_SMALL_MULTIPLES.gauge]: NEW_GAUGE_CHARTS_LIBRARY, - [CHARTS_TO_BE_DEPRECATED.pie]: NEW_PIE_CHARTS_LIBRARY, } as const; export const isSplitChart = (chartType: string | undefined, aggs?: AggConfigs) => { diff --git a/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts b/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts index 2447a122a77aa..f3e290e8b8e45 100644 --- a/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts +++ b/test/functional/apps/dashboard/group1/dashboard_unsaved_state.ts @@ -23,8 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let unsavedPanelCount = 0; const testQuery = 'Test Query'; - // FLAKY https://github.com/elastic/kibana/issues/112812 - describe.skip('dashboard unsaved state', () => { + describe('dashboard unsaved state', () => { before(async () => { await kibanaServer.savedObjects.cleanStandardList(); await kibanaServer.importExport.load( diff --git a/test/functional/apps/dashboard/group3/dashboard_state.ts b/test/functional/apps/dashboard/group3/dashboard_state.ts index 2c79f1fd61d23..c280de155be82 100644 --- a/test/functional/apps/dashboard/group3/dashboard_state.ts +++ b/test/functional/apps/dashboard/group3/dashboard_state.ts @@ -29,7 +29,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const pieChart = getService('pieChart'); const retry = getService('retry'); const elasticChart = getService('elasticChart'); - const kibanaServer = getService('kibanaServer'); const dashboardAddPanel = getService('dashboardAddPanel'); const xyChartSelector = 'xyVisChart'; @@ -43,18 +42,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Failing: See https://github.com/elastic/kibana/issues/139762 describe.skip('dashboard state', function describeIndexTests() { // Used to track flag before and after reset - let isNewChartsLibraryEnabled = true; before(async function () { - isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); await PageObjects.dashboard.initTests(); await PageObjects.dashboard.preserveCrossAppState(); - if (!isNewChartsLibraryEnabled) { - await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': true, - }); - } await browser.refresh(); }); @@ -288,9 +280,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('resets a pie slice color to the original when removed', async function () { const currentUrl = await getUrlFromShare(); - const newUrl = isNewChartsLibraryEnabled - ? currentUrl.replace(`'80000':%23FFFFFF`, '') - : currentUrl.replace(`vis:(colors:('80,000':%23FFFFFF))`, ''); + const newUrl = currentUrl.replace(`'80000':%23FFFFFF`, ''); await hardRefresh(newUrl); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/dashboard/group6/dashboard_back_button.ts b/test/functional/apps/dashboard/group5/dashboard_back_button.ts similarity index 100% rename from test/functional/apps/dashboard/group6/dashboard_back_button.ts rename to test/functional/apps/dashboard/group5/dashboard_back_button.ts diff --git a/test/functional/apps/dashboard/group6/dashboard_error_handling.ts b/test/functional/apps/dashboard/group5/dashboard_error_handling.ts similarity index 100% rename from test/functional/apps/dashboard/group6/dashboard_error_handling.ts rename to test/functional/apps/dashboard/group5/dashboard_error_handling.ts diff --git a/test/functional/apps/dashboard/group6/dashboard_options.ts b/test/functional/apps/dashboard/group5/dashboard_options.ts similarity index 100% rename from test/functional/apps/dashboard/group6/dashboard_options.ts rename to test/functional/apps/dashboard/group5/dashboard_options.ts diff --git a/test/functional/apps/dashboard/group6/dashboard_query_bar.ts b/test/functional/apps/dashboard/group5/dashboard_query_bar.ts similarity index 100% rename from test/functional/apps/dashboard/group6/dashboard_query_bar.ts rename to test/functional/apps/dashboard/group5/dashboard_query_bar.ts diff --git a/test/functional/apps/dashboard/group6/data_shared_attributes.ts b/test/functional/apps/dashboard/group5/data_shared_attributes.ts similarity index 100% rename from test/functional/apps/dashboard/group6/data_shared_attributes.ts rename to test/functional/apps/dashboard/group5/data_shared_attributes.ts diff --git a/test/functional/apps/dashboard/group6/embed_mode.ts b/test/functional/apps/dashboard/group5/embed_mode.ts similarity index 100% rename from test/functional/apps/dashboard/group6/embed_mode.ts rename to test/functional/apps/dashboard/group5/embed_mode.ts diff --git a/test/functional/apps/dashboard/group6/empty_dashboard.ts b/test/functional/apps/dashboard/group5/empty_dashboard.ts similarity index 100% rename from test/functional/apps/dashboard/group6/empty_dashboard.ts rename to test/functional/apps/dashboard/group5/empty_dashboard.ts diff --git a/test/functional/apps/dashboard/group5/index.ts b/test/functional/apps/dashboard/group5/index.ts index e05c980dda351..f78f7e2d549b8 100644 --- a/test/functional/apps/dashboard/group5/index.ts +++ b/test/functional/apps/dashboard/group5/index.ts @@ -11,38 +11,36 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - async function loadLogstash() { - await browser.setWindowSize(1200, 900); - await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + async function loadCurrentData() { + await browser.setWindowSize(1300, 900); + await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/dashboard/current/data'); } - async function unloadLogstash() { - await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + async function unloadCurrentData() { + await esArchiver.unload('test/functional/fixtures/es_archiver/dashboard/current/data'); } - describe('dashboard app - group 5', function () { - // TODO: Remove when vislib is removed - // https://github.com/elastic/kibana/issues/56143 - describe('old charts library', function () { - before(async () => { - await loadLogstash(); - await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': true, - }); - await browser.refresh(); - }); + describe('dashboard app - group 1', function () { + before(loadCurrentData); + after(unloadCurrentData); - after(async () => { - await unloadLogstash(); - await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': false, - }); - await browser.refresh(); - }); + // This has to be first since the other tests create some embeddables as side affects and our counting assumes + // a fresh index. + loadTestFile(require.resolve('./empty_dashboard')); + loadTestFile(require.resolve('./dashboard_options')); + loadTestFile(require.resolve('./data_shared_attributes')); + loadTestFile(require.resolve('./share')); + loadTestFile(require.resolve('./embed_mode')); + loadTestFile(require.resolve('./dashboard_back_button')); + loadTestFile(require.resolve('./dashboard_error_handling')); + loadTestFile(require.resolve('./legacy_urls')); + loadTestFile(require.resolve('./saved_search_embeddable')); - loadTestFile(require.resolve('../group3/dashboard_state')); - }); + // Note: This one must be last because it unloads some data for one of its tests! + // No, this isn't ideal, but loading/unloading takes so much time and these are all bunched + // to improve efficiency... + loadTestFile(require.resolve('./dashboard_query_bar')); }); } diff --git a/test/functional/apps/dashboard/group6/legacy_urls.ts b/test/functional/apps/dashboard/group5/legacy_urls.ts similarity index 100% rename from test/functional/apps/dashboard/group6/legacy_urls.ts rename to test/functional/apps/dashboard/group5/legacy_urls.ts diff --git a/test/functional/apps/dashboard/group6/saved_search_embeddable.ts b/test/functional/apps/dashboard/group5/saved_search_embeddable.ts similarity index 100% rename from test/functional/apps/dashboard/group6/saved_search_embeddable.ts rename to test/functional/apps/dashboard/group5/saved_search_embeddable.ts diff --git a/test/functional/apps/dashboard/group6/share.ts b/test/functional/apps/dashboard/group5/share.ts similarity index 100% rename from test/functional/apps/dashboard/group6/share.ts rename to test/functional/apps/dashboard/group5/share.ts diff --git a/test/functional/apps/dashboard/group6/index.ts b/test/functional/apps/dashboard/group6/index.ts deleted file mode 100644 index f78f7e2d549b8..0000000000000 --- a/test/functional/apps/dashboard/group6/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, loadTestFile }: FtrProviderContext) { - const browser = getService('browser'); - const esArchiver = getService('esArchiver'); - - async function loadCurrentData() { - await browser.setWindowSize(1300, 900); - await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); - await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/dashboard/current/data'); - } - - async function unloadCurrentData() { - await esArchiver.unload('test/functional/fixtures/es_archiver/dashboard/current/data'); - } - - describe('dashboard app - group 1', function () { - before(loadCurrentData); - after(unloadCurrentData); - - // This has to be first since the other tests create some embeddables as side affects and our counting assumes - // a fresh index. - loadTestFile(require.resolve('./empty_dashboard')); - loadTestFile(require.resolve('./dashboard_options')); - loadTestFile(require.resolve('./data_shared_attributes')); - loadTestFile(require.resolve('./share')); - loadTestFile(require.resolve('./embed_mode')); - loadTestFile(require.resolve('./dashboard_back_button')); - loadTestFile(require.resolve('./dashboard_error_handling')); - loadTestFile(require.resolve('./legacy_urls')); - loadTestFile(require.resolve('./saved_search_embeddable')); - - // Note: This one must be last because it unloads some data for one of its tests! - // No, this isn't ideal, but loading/unloading takes so much time and these are all bunched - // to improve efficiency... - loadTestFile(require.resolve('./dashboard_query_bar')); - }); -} diff --git a/test/functional/apps/discover/group1/_sidebar.ts b/test/functional/apps/discover/group1/_sidebar.ts index 109e8aa37cd38..db3abf5046582 100644 --- a/test/functional/apps/discover/group1/_sidebar.ts +++ b/test/functional/apps/discover/group1/_sidebar.ts @@ -510,6 +510,46 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(allFields.includes('_bytes-runtimefield')).to.be(false); }); + it('should render even when retrieving documents failed with an error', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.missingOrFail('discoverNoResultsError'); + + expect(await PageObjects.discover.getSidebarAriaDescription()).to.be( + '53 available fields. 0 empty fields. 3 meta fields.' + ); + + await PageObjects.discover.addRuntimeField('_invalid-runtimefield', `emit(‘’);`); + + await PageObjects.header.waitUntilLoadingHasFinished(); + + // error in fetching documents because of the invalid runtime field + await testSubjects.existOrFail('discoverNoResultsError'); + + await PageObjects.discover.waitUntilSidebarHasLoaded(); + + // check that the sidebar is rendered + expect(await PageObjects.discover.getSidebarAriaDescription()).to.be( + '54 available fields. 0 empty fields. 3 meta fields.' + ); + let allFields = await PageObjects.discover.getAllFieldNames(); + expect(allFields.includes('_invalid-runtimefield')).to.be(true); + + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.existOrFail('discoverNoResultsError'); // still has error + + // check that the sidebar is rendered event after a refresh + allFields = await PageObjects.discover.getAllFieldNames(); + expect(allFields.includes('_invalid-runtimefield')).to.be(true); + + await PageObjects.discover.removeField('_invalid-runtimefield'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSidebarHasLoaded(); + + await testSubjects.missingOrFail('discoverNoResultsError'); + }); + it('should work correctly when time range is updated', async function () { await esArchiver.loadIfNeeded( 'test/functional/fixtures/es_archiver/index_pattern_without_timefield' diff --git a/test/functional/apps/getting_started/_shakespeare.ts b/test/functional/apps/getting_started/_shakespeare.ts index 93e8066cb6296..5426594bee30a 100644 --- a/test/functional/apps/getting_started/_shakespeare.ts +++ b/test/functional/apps/getting_started/_shakespeare.ts @@ -17,7 +17,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const security = getService('security'); const config = getService('config'); - const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects([ 'console', @@ -44,21 +43,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // order they are added. let aggIndex = 1; // Used to track flag before and after reset - let isNewChartsLibraryEnabled = true; before(async function () { log.debug('https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html'); - isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); await security.testUser.setRoles(['kibana_admin', 'test_shakespeare_reader']); await kibanaServer.savedObjects.cleanStandardList(); log.debug('Load shakespeare data'); - - if (!isNewChartsLibraryEnabled) { - await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': true, - }); - await browser.refresh(); - } }); after(async () => { diff --git a/test/functional/apps/getting_started/index.ts b/test/functional/apps/getting_started/index.ts index a4c9b5077c80e..63ed07905a734 100644 --- a/test/functional/apps/getting_started/index.ts +++ b/test/functional/apps/getting_started/index.ts @@ -10,7 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); - const kibanaServer = getService('kibanaServer'); const config = getService('config'); const esNode = config.get('esTestCluster.ccs') ? getService('remoteEsArchiver' as 'esArchiver') @@ -26,25 +25,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await esNode.unload('test/functional/fixtures/es_archiver/getting_started/shakespeare'); }); - // TODO: Remove when vislib is removed - describe('old charts library', function () { - before(async () => { - await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': true, - }); - await browser.refresh(); - }); - - after(async () => { - await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': false, - }); - await browser.refresh(); - }); - - loadTestFile(require.resolve('./_shakespeare')); - }); - describe('new charts library', () => { loadTestFile(require.resolve('./_shakespeare')); }); diff --git a/test/functional/apps/visualize/group3/index.ts b/test/functional/apps/visualize/group3/index.ts index 4ce3def5c52e1..e8d6fa69932fd 100644 --- a/test/functional/apps/visualize/group3/index.ts +++ b/test/functional/apps/visualize/group3/index.ts @@ -22,17 +22,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/long_window_logstash'); - - await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': true, - }); - await browser.refresh(); - }); - - after(async () => { - await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': false, - }); await browser.refresh(); }); diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 6dd17c9c13e1c..be870ce24e127 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -59,7 +59,6 @@ export class VisualizePageObject extends FtrService { await this.kibanaServer.uiSettings.replace({ defaultIndex: this.defaultIndexString, [FORMATS_UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0,0.[000]b', - 'visualization:visualize:legacyPieChartsLibrary': !isNewLibrary, 'visualization:visualize:legacyHeatmapChartsLibrary': !isNewLibrary, 'histogram:maxBars': 100, }); diff --git a/x-pack/plugins/alerting/server/routes/bulk_delete_rules.test.ts b/x-pack/plugins/alerting/server/routes/bulk_delete_rules.test.ts index 8fc27cfbc0054..c15405be85253 100644 --- a/x-pack/plugins/alerting/server/routes/bulk_delete_rules.test.ts +++ b/x-pack/plugins/alerting/server/routes/bulk_delete_rules.test.ts @@ -26,7 +26,7 @@ beforeEach(() => { describe('bulkDeleteRulesRoute', () => { const bulkDeleteRequest = { filter: '' }; - const bulkDeleteResult = { errors: [], total: 1, taskIdsFailedToBeDeleted: [] }; + const bulkDeleteResult = { rules: [], errors: [], total: 1, taskIdsFailedToBeDeleted: [] }; it('should delete rules with proper parameters', async () => { const licenseState = licenseStateMock.create(); diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index ba7654694da26..e9c3c3a0cee73 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -42,7 +42,6 @@ const createRulesClientMock = () => { bulkDisableRules: jest.fn(), snooze: jest.fn(), unsnooze: jest.fn(), - calculateIsSnoozedUntil: jest.fn(), clearExpiredSnoozes: jest.fn(), runSoon: jest.fn(), clone: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.ts b/x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.ts new file mode 100644 index 0000000000000..e98fb875b74a7 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.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 { RawRule } from '../../types'; +import { CreateAPIKeyResult } from '../types'; + +export function apiKeyAsAlertAttributes( + apiKey: CreateAPIKeyResult | null, + username: string | null +): Pick { + return apiKey && apiKey.apiKeysEnabled + ? { + apiKeyOwner: username, + apiKey: Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64'), + } + : { + apiKeyOwner: null, + apiKey: null, + }; +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/apply_bulk_edit_operation.test.ts b/x-pack/plugins/alerting/server/rules_client/common/apply_bulk_edit_operation.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/apply_bulk_edit_operation.test.ts rename to x-pack/plugins/alerting/server/rules_client/common/apply_bulk_edit_operation.test.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/apply_bulk_edit_operation.ts b/x-pack/plugins/alerting/server/rules_client/common/apply_bulk_edit_operation.ts similarity index 95% rename from x-pack/plugins/alerting/server/rules_client/lib/apply_bulk_edit_operation.ts rename to x-pack/plugins/alerting/server/rules_client/common/apply_bulk_edit_operation.ts index 360bd0d72a5fa..e40d8e6c8c854 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/apply_bulk_edit_operation.ts +++ b/x-pack/plugins/alerting/server/rules_client/common/apply_bulk_edit_operation.ts @@ -5,7 +5,7 @@ * 2.0. */ import { set, get } from 'lodash'; -import type { BulkEditOperation, BulkEditFields } from '../rules_client'; +import type { BulkEditOperation, BulkEditFields } from '../types'; // defining an union type that will passed directly to generic function as a workaround for the issue similar to // https://github.com/microsoft/TypeScript/issues/29479 diff --git a/x-pack/plugins/alerting/server/rules_client/audit_events.test.ts b/x-pack/plugins/alerting/server/rules_client/common/audit_events.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/audit_events.test.ts rename to x-pack/plugins/alerting/server/rules_client/common/audit_events.test.ts diff --git a/x-pack/plugins/alerting/server/rules_client/audit_events.ts b/x-pack/plugins/alerting/server/rules_client/common/audit_events.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/audit_events.ts rename to x-pack/plugins/alerting/server/rules_client/common/audit_events.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/build_kuery_node_filter.test.ts b/x-pack/plugins/alerting/server/rules_client/common/build_kuery_node_filter.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/build_kuery_node_filter.test.ts rename to x-pack/plugins/alerting/server/rules_client/common/build_kuery_node_filter.test.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/build_kuery_node_filter.ts b/x-pack/plugins/alerting/server/rules_client/common/build_kuery_node_filter.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/build_kuery_node_filter.ts rename to x-pack/plugins/alerting/server/rules_client/common/build_kuery_node_filter.ts diff --git a/x-pack/plugins/alerting/server/rules_client/common/calculate_is_snoozed_until.ts b/x-pack/plugins/alerting/server/rules_client/common/calculate_is_snoozed_until.ts new file mode 100644 index 0000000000000..d77a013ae3f6b --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/calculate_is_snoozed_until.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RuleSnooze } from '../../types'; +import { getRuleSnoozeEndTime } from '../../lib'; + +export function calculateIsSnoozedUntil(rule: { + muteAll: boolean; + snoozeSchedule?: RuleSnooze; +}): string | null { + const isSnoozedUntil = getRuleSnoozeEndTime(rule); + return isSnoozedUntil ? isSnoozedUntil.toISOString() : null; +} diff --git a/x-pack/plugins/alerting/server/rules_client/common/constants.ts b/x-pack/plugins/alerting/server/rules_client/common/constants.ts new file mode 100644 index 0000000000000..d72f80d41a91e --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/constants.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 { + AlertingAuthorizationFilterType, + AlertingAuthorizationFilterOpts, +} from '../../authorization'; + +// NOTE: Changing this prefix will require a migration to update the prefix in all existing `rule` saved objects +export const extractedSavedObjectParamReferenceNamePrefix = 'param:'; + +// NOTE: Changing this prefix will require a migration to update the prefix in all existing `rule` saved objects +export const preconfiguredConnectorActionRefPrefix = 'preconfigured:'; + +export const alertingAuthorizationFilterOpts: AlertingAuthorizationFilterOpts = { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { ruleTypeId: 'alert.attributes.alertTypeId', consumer: 'alert.attributes.consumer' }, +}; + +export const MAX_RULES_NUMBER_FOR_BULK_OPERATION = 10000; +export const API_KEY_GENERATE_CONCURRENCY = 50; +export const RULE_TYPE_CHECKS_CONCURRENCY = 50; diff --git a/x-pack/plugins/alerting/server/rules_client/common/generate_api_key_name.ts b/x-pack/plugins/alerting/server/rules_client/common/generate_api_key_name.ts new file mode 100644 index 0000000000000..bb5c8ae9bac23 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/generate_api_key_name.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { truncate, trim } from 'lodash'; + +export function generateAPIKeyName(alertTypeId: string, alertName: string) { + return truncate(`Alerting: ${alertTypeId}/${trim(alertName)}`, { length: 256 }); +} diff --git a/x-pack/plugins/alerting/server/rules_client/common/get_and_validate_common_bulk_options.ts b/x-pack/plugins/alerting/server/rules_client/common/get_and_validate_common_bulk_options.ts new file mode 100644 index 0000000000000..e4497cce1e30b --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/get_and_validate_common_bulk_options.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 Boom from '@hapi/boom'; +import { BulkOptions, BulkOptionsFilter, BulkOptionsIds } from '../types'; + +export const getAndValidateCommonBulkOptions = (options: BulkOptions) => { + const filter = (options as BulkOptionsFilter).filter; + const ids = (options as BulkOptionsIds).ids; + + if (!ids && !filter) { + throw Boom.badRequest( + "Either 'ids' or 'filter' property in method's arguments should be provided" + ); + } + + if (ids?.length === 0) { + throw Boom.badRequest("'ids' property should not be an empty array"); + } + + if (ids && filter) { + throw Boom.badRequest( + "Both 'filter' and 'ids' are supplied. Define either 'ids' or 'filter' properties in method's arguments" + ); + } + return { ids, filter }; +}; diff --git a/x-pack/plugins/alerting/server/rules_client/common/include_fields_required_for_authentication.ts b/x-pack/plugins/alerting/server/rules_client/common/include_fields_required_for_authentication.ts new file mode 100644 index 0000000000000..b89d56174c59b --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/include_fields_required_for_authentication.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { uniq } from 'lodash'; + +export function includeFieldsRequiredForAuthentication(fields: string[]): string[] { + return uniq([...fields, 'alertTypeId', 'consumer']); +} diff --git a/x-pack/plugins/alerting/server/rules_client/common/index.ts b/x-pack/plugins/alerting/server/rules_client/common/index.ts new file mode 100644 index 0000000000000..984c288095d4c --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { mapSortField } from './map_sort_field'; +export { validateOperationOnAttributes } from './validate_attributes'; +export { retryIfBulkEditConflicts } from './retry_if_bulk_edit_conflicts'; +export { retryIfBulkDisableConflicts } from './retry_if_bulk_disable_conflicts'; +export { retryIfBulkOperationConflicts } from './retry_if_bulk_operation_conflicts'; +export { applyBulkEditOperation } from './apply_bulk_edit_operation'; +export { buildKueryNodeFilter } from './build_kuery_node_filter'; +export { generateAPIKeyName } from './generate_api_key_name'; +export * from './mapped_params_utils'; +export { apiKeyAsAlertAttributes } from './api_key_as_alert_attributes'; +export { calculateIsSnoozedUntil } from './calculate_is_snoozed_until'; +export * from './inject_references'; +export { parseDate } from './parse_date'; +export { includeFieldsRequiredForAuthentication } from './include_fields_required_for_authentication'; +export { getAndValidateCommonBulkOptions } from './get_and_validate_common_bulk_options'; +export * from './snooze_utils'; diff --git a/x-pack/plugins/alerting/server/rules_client/common/inject_references.ts b/x-pack/plugins/alerting/server/rules_client/common/inject_references.ts new file mode 100644 index 0000000000000..07565240ed5c4 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/inject_references.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 Boom from '@hapi/boom'; +import { omit } from 'lodash'; +import { SavedObjectReference, SavedObjectAttributes } from '@kbn/core/server'; +import { UntypedNormalizedRuleType } from '../../rule_type_registry'; +import { Rule, RawRule, RuleTypeParams } from '../../types'; +import { + preconfiguredConnectorActionRefPrefix, + extractedSavedObjectParamReferenceNamePrefix, +} from './constants'; + +export function injectReferencesIntoActions( + alertId: string, + actions: RawRule['actions'], + references: SavedObjectReference[] +) { + return actions.map((action) => { + if (action.actionRef.startsWith(preconfiguredConnectorActionRefPrefix)) { + return { + ...omit(action, 'actionRef'), + id: action.actionRef.replace(preconfiguredConnectorActionRefPrefix, ''), + }; + } + + const reference = references.find((ref) => ref.name === action.actionRef); + if (!reference) { + throw new Error(`Action reference "${action.actionRef}" not found in alert id: ${alertId}`); + } + return { + ...omit(action, 'actionRef'), + id: reference.id, + }; + }) as Rule['actions']; +} + +export function injectReferencesIntoParams< + Params extends RuleTypeParams, + ExtractedParams extends RuleTypeParams +>( + ruleId: string, + ruleType: UntypedNormalizedRuleType, + ruleParams: SavedObjectAttributes | undefined, + references: SavedObjectReference[] +): Params { + try { + const paramReferences = references + .filter((reference: SavedObjectReference) => + reference.name.startsWith(extractedSavedObjectParamReferenceNamePrefix) + ) + .map((reference: SavedObjectReference) => ({ + ...reference, + name: reference.name.replace(extractedSavedObjectParamReferenceNamePrefix, ''), + })); + return ruleParams && ruleType?.useSavedObjectReferences?.injectReferences + ? (ruleType.useSavedObjectReferences.injectReferences( + ruleParams as ExtractedParams, + paramReferences + ) as Params) + : (ruleParams as Params); + } catch (err) { + throw Boom.badRequest( + `Error injecting reference into rule params for rule id ${ruleId} - ${err.message}` + ); + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/map_sort_field.test.ts b/x-pack/plugins/alerting/server/rules_client/common/map_sort_field.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/map_sort_field.test.ts rename to x-pack/plugins/alerting/server/rules_client/common/map_sort_field.test.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/map_sort_field.ts b/x-pack/plugins/alerting/server/rules_client/common/map_sort_field.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/map_sort_field.ts rename to x-pack/plugins/alerting/server/rules_client/common/map_sort_field.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/mapped_params_utils.test.ts b/x-pack/plugins/alerting/server/rules_client/common/mapped_params_utils.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/mapped_params_utils.test.ts rename to x-pack/plugins/alerting/server/rules_client/common/mapped_params_utils.test.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/mapped_params_utils.ts b/x-pack/plugins/alerting/server/rules_client/common/mapped_params_utils.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/mapped_params_utils.ts rename to x-pack/plugins/alerting/server/rules_client/common/mapped_params_utils.ts diff --git a/x-pack/plugins/alerting/server/rules_client/common/parse_date.ts b/x-pack/plugins/alerting/server/rules_client/common/parse_date.ts new file mode 100644 index 0000000000000..21c005605ea6f --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/parse_date.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 Boom from '@hapi/boom'; +import { i18n } from '@kbn/i18n'; +import { parseIsoOrRelativeDate } from '../../lib/iso_or_relative_date'; + +export function parseDate( + dateString: string | undefined, + propertyName: string, + defaultValue: Date +): Date { + if (dateString === undefined) { + return defaultValue; + } + + const parsedDate = parseIsoOrRelativeDate(dateString); + if (parsedDate === undefined) { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.invalidDate', { + defaultMessage: 'Invalid date for parameter {field}: "{dateValue}"', + values: { + field: propertyName, + dateValue: dateString, + }, + }) + ); + } + + return parsedDate; +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_disable_conflicts.ts b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_disable_conflicts.ts similarity index 98% rename from x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_disable_conflicts.ts rename to x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_disable_conflicts.ts index 90ecf57c029a0..29fd62b9333f0 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_disable_conflicts.ts +++ b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_disable_conflicts.ts @@ -10,7 +10,7 @@ import { chunk } from 'lodash'; import { KueryNode } from '@kbn/es-query'; import { Logger, SavedObjectsBulkUpdateObject } from '@kbn/core/server'; import { convertRuleIdsToKueryNode } from '../../lib'; -import { BulkOperationError } from '../rules_client'; +import { BulkOperationError } from '../types'; import { waitBeforeNextRetry, RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; import { RawRule } from '../../types'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.test.ts b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.test.ts rename to x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.test.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.ts similarity index 98% rename from x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts rename to x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.ts index d893f2e9b5df8..984ce17d149e0 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts +++ b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_edit_conflicts.ts @@ -10,7 +10,7 @@ import { chunk } from 'lodash'; import { KueryNode } from '@kbn/es-query'; import { Logger, SavedObjectsBulkUpdateObject, SavedObjectsUpdateResponse } from '@kbn/core/server'; import { convertRuleIdsToKueryNode } from '../../lib'; -import { BulkOperationError } from '../rules_client'; +import { BulkOperationError } from '../types'; import { RawRule } from '../../types'; import { waitBeforeNextRetry, RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_operation_conflicts.test.ts b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_operation_conflicts.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_operation_conflicts.test.ts rename to x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_operation_conflicts.test.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_operation_conflicts.ts b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_operation_conflicts.ts similarity index 98% rename from x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_operation_conflicts.ts rename to x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_operation_conflicts.ts index 9b95335f3994c..4210de1207623 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_operation_conflicts.ts +++ b/x-pack/plugins/alerting/server/rules_client/common/retry_if_bulk_operation_conflicts.ts @@ -10,7 +10,7 @@ import { chunk } from 'lodash'; import { KueryNode } from '@kbn/es-query'; import { Logger, SavedObjectsBulkUpdateObject } from '@kbn/core/server'; import { convertRuleIdsToKueryNode } from '../../lib'; -import { BulkOperationError } from '../rules_client'; +import { BulkOperationError } from '../types'; import { waitBeforeNextRetry, RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; import { RawRule } from '../../types'; diff --git a/x-pack/plugins/alerting/server/rules_client/common/snooze_utils.ts b/x-pack/plugins/alerting/server/rules_client/common/snooze_utils.ts new file mode 100644 index 0000000000000..2a6d1b3b06e7a --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/common/snooze_utils.ts @@ -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 { i18n } from '@kbn/i18n'; +import { RawRule, RuleSnoozeSchedule } from '../../types'; +import { getActiveScheduledSnoozes } from '../../lib/is_rule_snoozed'; + +export function getSnoozeAttributes(attributes: RawRule, snoozeSchedule: RuleSnoozeSchedule) { + // If duration is -1, instead mute all + const { id: snoozeId, duration } = snoozeSchedule; + + if (duration === -1) { + return { + muteAll: true, + snoozeSchedule: clearUnscheduledSnooze(attributes), + }; + } + return { + snoozeSchedule: (snoozeId + ? clearScheduledSnoozesById(attributes, [snoozeId]) + : clearUnscheduledSnooze(attributes) + ).concat(snoozeSchedule), + muteAll: false, + }; +} + +export function getBulkSnoozeAttributes(attributes: RawRule, snoozeSchedule: RuleSnoozeSchedule) { + // If duration is -1, instead mute all + const { id: snoozeId, duration } = snoozeSchedule; + + if (duration === -1) { + return { + muteAll: true, + snoozeSchedule: clearUnscheduledSnooze(attributes), + }; + } + + // Bulk adding snooze schedule, don't touch the existing snooze/indefinite snooze + if (snoozeId) { + const existingSnoozeSchedules = attributes.snoozeSchedule || []; + return { + muteAll: attributes.muteAll, + snoozeSchedule: [...existingSnoozeSchedules, snoozeSchedule], + }; + } + + // Bulk snoozing, don't touch the existing snooze schedules + return { + muteAll: false, + snoozeSchedule: [...clearUnscheduledSnooze(attributes), snoozeSchedule], + }; +} + +export function getUnsnoozeAttributes(attributes: RawRule, scheduleIds?: string[]) { + const snoozeSchedule = scheduleIds + ? clearScheduledSnoozesById(attributes, scheduleIds) + : clearCurrentActiveSnooze(attributes); + + return { + snoozeSchedule, + ...(!scheduleIds ? { muteAll: false } : {}), + }; +} + +export function getBulkUnsnoozeAttributes(attributes: RawRule, scheduleIds?: string[]) { + // Bulk removing snooze schedules, don't touch the current snooze/indefinite snooze + if (scheduleIds) { + const newSchedules = clearScheduledSnoozesById(attributes, scheduleIds); + // Unscheduled snooze is also known as snooze now + const unscheduledSnooze = + attributes.snoozeSchedule?.filter((s) => typeof s.id === 'undefined') || []; + + return { + snoozeSchedule: [...unscheduledSnooze, ...newSchedules], + muteAll: attributes.muteAll, + }; + } + + // Bulk unsnoozing, don't touch current snooze schedules that are NOT active + return { + snoozeSchedule: clearCurrentActiveSnooze(attributes), + muteAll: false, + }; +} + +export function clearUnscheduledSnooze(attributes: RawRule) { + // Clear any snoozes that have no ID property. These are "simple" snoozes created with the quick UI, e.g. snooze for 3 days starting now + return attributes.snoozeSchedule + ? attributes.snoozeSchedule.filter((s) => typeof s.id !== 'undefined') + : []; +} + +export function clearScheduledSnoozesById(attributes: RawRule, ids: string[]) { + return attributes.snoozeSchedule + ? attributes.snoozeSchedule.filter((s) => s.id && !ids.includes(s.id)) + : []; +} + +export function clearCurrentActiveSnooze(attributes: RawRule) { + // First attempt to cancel a simple (unscheduled) snooze + const clearedUnscheduledSnoozes = clearUnscheduledSnooze(attributes); + // Now clear any scheduled snoozes that are currently active and never recur + const activeSnoozes = getActiveScheduledSnoozes(attributes); + const activeSnoozeIds = activeSnoozes?.map((s) => s.id) ?? []; + const recurringSnoozesToSkip: string[] = []; + const clearedNonRecurringActiveSnoozes = clearedUnscheduledSnoozes.filter((s) => { + if (!activeSnoozeIds.includes(s.id!)) return true; + // Check if this is a recurring snooze, and return true if so + if (s.rRule.freq && s.rRule.count !== 1) { + recurringSnoozesToSkip.push(s.id!); + return true; + } + }); + const clearedSnoozesAndSkippedRecurringSnoozes = clearedNonRecurringActiveSnoozes.map((s) => { + if (s.id && !recurringSnoozesToSkip.includes(s.id)) return s; + const currentRecurrence = activeSnoozes?.find((a) => a.id === s.id)?.lastOccurrence; + if (!currentRecurrence) return s; + return { + ...s, + skipRecurrences: (s.skipRecurrences ?? []).concat(currentRecurrence.toISOString()), + }; + }); + return clearedSnoozesAndSkippedRecurringSnoozes; +} + +export function verifySnoozeScheduleLimit(attributes: Partial) { + const schedules = attributes.snoozeSchedule?.filter((snooze) => snooze.id); + if (schedules && schedules.length > 5) { + throw Error( + i18n.translate('xpack.alerting.rulesClient.snoozeSchedule.limitReached', { + defaultMessage: 'Rule cannot have more than 5 snooze schedules', + }) + ); + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_attributes.test.ts b/x-pack/plugins/alerting/server/rules_client/common/validate_attributes.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/validate_attributes.test.ts rename to x-pack/plugins/alerting/server/rules_client/common/validate_attributes.test.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_attributes.ts b/x-pack/plugins/alerting/server/rules_client/common/validate_attributes.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/validate_attributes.ts rename to x-pack/plugins/alerting/server/rules_client/common/validate_attributes.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.test.ts b/x-pack/plugins/alerting/server/rules_client/common/wait_before_next_retry.test.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.test.ts rename to x-pack/plugins/alerting/server/rules_client/common/wait_before_next_retry.test.ts diff --git a/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.ts b/x-pack/plugins/alerting/server/rules_client/common/wait_before_next_retry.ts similarity index 100% rename from x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.ts rename to x-pack/plugins/alerting/server/rules_client/common/wait_before_next_retry.ts diff --git a/x-pack/plugins/alerting/server/rules_client/index.ts b/x-pack/plugins/alerting/server/rules_client/index.ts index e5f2b18ee82d1..ed2b5a8558368 100644 --- a/x-pack/plugins/alerting/server/rules_client/index.ts +++ b/x-pack/plugins/alerting/server/rules_client/index.ts @@ -6,3 +6,4 @@ */ export * from './rules_client'; +export * from './types'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/check_authorization_and_get_total.ts b/x-pack/plugins/alerting/server/rules_client/lib/check_authorization_and_get_total.ts new file mode 100644 index 0000000000000..ecaa7fd172fa7 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/check_authorization_and_get_total.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import pMap from 'p-map'; +import Boom from '@hapi/boom'; +import { KueryNode } from '@kbn/es-query'; +import { RawRule } from '../../types'; +import { WriteOperations, ReadOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { BulkAction, RuleBulkOperationAggregation } from '../types'; +import { + MAX_RULES_NUMBER_FOR_BULK_OPERATION, + RULE_TYPE_CHECKS_CONCURRENCY, +} from '../common/constants'; +import { RulesClientContext } from '../types'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; + +export const checkAuthorizationAndGetTotal = async ( + context: RulesClientContext, + { + filter, + action, + }: { + filter: KueryNode | null; + action: BulkAction; + } +) => { + const actionToConstantsMapping: Record< + BulkAction, + { WriteOperation: WriteOperations | ReadOperations; RuleAuditAction: RuleAuditAction } + > = { + DELETE: { + WriteOperation: WriteOperations.BulkDelete, + RuleAuditAction: RuleAuditAction.DELETE, + }, + ENABLE: { + WriteOperation: WriteOperations.BulkEnable, + RuleAuditAction: RuleAuditAction.ENABLE, + }, + DISABLE: { + WriteOperation: WriteOperations.BulkDisable, + RuleAuditAction: RuleAuditAction.DISABLE, + }, + }; + const { aggregations, total } = await context.unsecuredSavedObjectsClient.find< + RawRule, + RuleBulkOperationAggregation + >({ + filter, + page: 1, + perPage: 0, + type: 'alert', + aggs: { + alertTypeId: { + multi_terms: { + terms: [ + { field: 'alert.attributes.alertTypeId' }, + { field: 'alert.attributes.consumer' }, + ], + }, + }, + }, + }); + + if (total > MAX_RULES_NUMBER_FOR_BULK_OPERATION) { + throw Boom.badRequest( + `More than ${MAX_RULES_NUMBER_FOR_BULK_OPERATION} rules matched for bulk ${action.toLocaleLowerCase()}` + ); + } + + const buckets = aggregations?.alertTypeId.buckets; + + if (buckets === undefined || buckets?.length === 0) { + throw Boom.badRequest(`No rules found for bulk ${action.toLocaleLowerCase()}`); + } + + await pMap( + buckets, + async ({ key: [ruleType, consumer] }) => { + context.ruleTypeRegistry.ensureRuleTypeEnabled(ruleType); + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: ruleType, + consumer, + operation: actionToConstantsMapping[action].WriteOperation, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: actionToConstantsMapping[action].RuleAuditAction, + error, + }) + ); + throw error; + } + }, + { concurrency: RULE_TYPE_CHECKS_CONCURRENCY } + ); + return { total }; +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.ts b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.ts new file mode 100644 index 0000000000000..202ab4d23972d --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Boom from '@hapi/boom'; +import { RawRule } from '../../types'; +import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common'; +import { RulesClientContext } from '../types'; + +export async function createNewAPIKeySet( + context: RulesClientContext, + { + attributes, + username, + }: { + attributes: RawRule; + username: string | null; + } +): Promise> { + let createdAPIKey = null; + try { + createdAPIKey = await context.createAPIKey( + generateAPIKeyName(attributes.alertTypeId, attributes.name) + ); + } catch (error) { + throw Boom.badRequest(`Error creating API key for rule: ${error.message}`); + } + + return apiKeyAsAlertAttributes(createdAPIKey, username); +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/create_rule_saved_object.ts b/x-pack/plugins/alerting/server/rules_client/lib/create_rule_saved_object.ts new file mode 100644 index 0000000000000..45ade4086af4a --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/create_rule_saved_object.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectReference, SavedObject } from '@kbn/core/server'; +import { RawRule, RuleTypeParams } from '../../types'; +import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { SavedObjectOptions } from '../types'; +import { RulesClientContext } from '../types'; +import { updateMeta } from './update_meta'; +import { scheduleTask } from './schedule_task'; +import { getAlertFromRaw } from './get_alert_from_raw'; + +export async function createRuleSavedObject( + context: RulesClientContext, + { + intervalInMs, + rawRule, + references, + ruleId, + options, + }: { + intervalInMs: number; + rawRule: RawRule; + references: SavedObjectReference[]; + ruleId: string; + options?: SavedObjectOptions; + } +) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.CREATE, + outcome: 'unknown', + savedObject: { type: 'alert', id: ruleId }, + }) + ); + + let createdAlert: SavedObject; + try { + createdAlert = await context.unsecuredSavedObjectsClient.create( + 'alert', + updateMeta(context, rawRule), + { + ...options, + references, + id: ruleId, + } + ); + } catch (e) { + // Avoid unused API key + await bulkMarkApiKeysForInvalidation( + { apiKeys: rawRule.apiKey ? [rawRule.apiKey] : [] }, + context.logger, + context.unsecuredSavedObjectsClient + ); + + throw e; + } + if (rawRule.enabled) { + let scheduledTask; + try { + scheduledTask = await scheduleTask(context, { + id: createdAlert.id, + consumer: rawRule.consumer, + ruleTypeId: rawRule.alertTypeId, + schedule: rawRule.schedule, + throwOnConflict: true, + }); + } catch (e) { + // Cleanup data, something went wrong scheduling the task + try { + await context.unsecuredSavedObjectsClient.delete('alert', createdAlert.id); + } catch (err) { + // Skip the cleanup error and throw the task manager error to avoid confusion + context.logger.error( + `Failed to cleanup alert "${createdAlert.id}" after scheduling task failed. Error: ${err.message}` + ); + } + throw e; + } + await context.unsecuredSavedObjectsClient.update('alert', createdAlert.id, { + scheduledTaskId: scheduledTask.id, + }); + createdAlert.attributes.scheduledTaskId = scheduledTask.id; + } + + // Log warning if schedule interval is less than the minimum but we're not enforcing it + if ( + intervalInMs < context.minimumScheduleIntervalInMs && + !context.minimumScheduleInterval.enforce + ) { + context.logger.warn( + `Rule schedule interval (${rawRule.schedule.interval}) for "${createdAlert.attributes.alertTypeId}" rule type with ID "${createdAlert.id}" is less than the minimum value (${context.minimumScheduleInterval.value}). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent creation of these rules.` + ); + } + + return getAlertFromRaw( + context, + createdAlert.id, + createdAlert.attributes.alertTypeId, + createdAlert.attributes, + references, + false, + true + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/denormalize_actions.ts b/x-pack/plugins/alerting/server/rules_client/lib/denormalize_actions.ts new file mode 100644 index 0000000000000..0f7a164d2a741 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/denormalize_actions.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectReference } from '@kbn/core/server'; +import { RawRule } from '../../types'; +import { preconfiguredConnectorActionRefPrefix } from '../common/constants'; +import { RulesClientContext } from '../types'; +import { NormalizedAlertAction } from '../types'; + +export async function denormalizeActions( + context: RulesClientContext, + alertActions: NormalizedAlertAction[] +): Promise<{ actions: RawRule['actions']; references: SavedObjectReference[] }> { + const references: SavedObjectReference[] = []; + const actions: RawRule['actions'] = []; + if (alertActions.length) { + const actionsClient = await context.getActionsClient(); + const actionIds = [...new Set(alertActions.map((alertAction) => alertAction.id))]; + const actionResults = await actionsClient.getBulk(actionIds); + const actionTypeIds = [...new Set(actionResults.map((action) => action.actionTypeId))]; + actionTypeIds.forEach((id) => { + // Notify action type usage via "isActionTypeEnabled" function + actionsClient.isActionTypeEnabled(id, { notifyUsage: true }); + }); + alertActions.forEach(({ id, ...alertAction }, i) => { + const actionResultValue = actionResults.find((action) => action.id === id); + if (actionResultValue) { + if (actionsClient.isPreconfigured(id)) { + actions.push({ + ...alertAction, + actionRef: `${preconfiguredConnectorActionRefPrefix}${id}`, + actionTypeId: actionResultValue.actionTypeId, + }); + } else { + const actionRef = `action_${i}`; + references.push({ + id, + name: actionRef, + type: 'action', + }); + actions.push({ + ...alertAction, + actionRef, + actionTypeId: actionResultValue.actionTypeId, + }); + } + } else { + actions.push({ + ...alertAction, + actionRef: '', + actionTypeId: '', + }); + } + }); + } + return { + actions, + references, + }; +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/extract_references.ts b/x-pack/plugins/alerting/server/rules_client/lib/extract_references.ts new file mode 100644 index 0000000000000..58f6f6ab20dbc --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/extract_references.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectReference } from '@kbn/core/server'; +import { RawRule, RuleTypeParams } from '../../types'; +import { UntypedNormalizedRuleType } from '../../rule_type_registry'; +import { NormalizedAlertAction } from '../types'; +import { extractedSavedObjectParamReferenceNamePrefix } from '../common/constants'; +import { RulesClientContext } from '../types'; +import { denormalizeActions } from './denormalize_actions'; + +export async function extractReferences< + Params extends RuleTypeParams, + ExtractedParams extends RuleTypeParams +>( + context: RulesClientContext, + ruleType: UntypedNormalizedRuleType, + ruleActions: NormalizedAlertAction[], + ruleParams: Params +): Promise<{ + actions: RawRule['actions']; + params: ExtractedParams; + references: SavedObjectReference[]; +}> { + const { references: actionReferences, actions } = await denormalizeActions(context, ruleActions); + + // Extracts any references using configured reference extractor if available + const extractedRefsAndParams = ruleType?.useSavedObjectReferences?.extractReferences + ? ruleType.useSavedObjectReferences.extractReferences(ruleParams) + : null; + const extractedReferences = extractedRefsAndParams?.references ?? []; + const params = (extractedRefsAndParams?.params as ExtractedParams) ?? ruleParams; + + // Prefix extracted references in order to avoid clashes with framework level references + const paramReferences = extractedReferences.map((reference: SavedObjectReference) => ({ + ...reference, + name: `${extractedSavedObjectParamReferenceNamePrefix}${reference.name}`, + })); + + const references = [...actionReferences, ...paramReferences]; + + return { + actions, + params, + references, + }; +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/get_alert_from_raw.ts b/x-pack/plugins/alerting/server/rules_client/lib/get_alert_from_raw.ts new file mode 100644 index 0000000000000..72cd5c0ec4b1a --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/get_alert_from_raw.ts @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { omit, isEmpty } from 'lodash'; +import { SavedObjectReference } from '@kbn/core/server'; +import { + Rule, + PartialRule, + RawRule, + IntervalSchedule, + RuleTypeParams, + RuleWithLegacyId, + PartialRuleWithLegacyId, +} from '../../types'; +import { ruleExecutionStatusFromRaw, convertMonitoringFromRawAndVerify } from '../../lib'; +import { UntypedNormalizedRuleType } from '../../rule_type_registry'; +import { getActiveScheduledSnoozes } from '../../lib/is_rule_snoozed'; +import { + calculateIsSnoozedUntil, + injectReferencesIntoActions, + injectReferencesIntoParams, +} from '../common'; +import { RulesClientContext } from '../types'; + +export function getAlertFromRaw( + context: RulesClientContext, + id: string, + ruleTypeId: string, + rawRule: RawRule, + references: SavedObjectReference[] | undefined, + includeLegacyId: boolean = false, + excludeFromPublicApi: boolean = false, + includeSnoozeData: boolean = false +): Rule | RuleWithLegacyId { + const ruleType = context.ruleTypeRegistry.get(ruleTypeId); + // In order to support the partial update API of Saved Objects we have to support + // partial updates of an Alert, but when we receive an actual RawRule, it is safe + // to cast the result to an Alert + const res = getPartialRuleFromRaw( + context, + id, + ruleType, + rawRule, + references, + includeLegacyId, + excludeFromPublicApi, + includeSnoozeData + ); + // include to result because it is for internal rules client usage + if (includeLegacyId) { + return res as RuleWithLegacyId; + } + // exclude from result because it is an internal variable + return omit(res, ['legacyId']) as Rule; +} + +export function getPartialRuleFromRaw( + context: RulesClientContext, + id: string, + ruleType: UntypedNormalizedRuleType, + { + createdAt, + updatedAt, + meta, + notifyWhen, + legacyId, + scheduledTaskId, + params, + executionStatus, + monitoring, + nextRun, + schedule, + actions, + snoozeSchedule, + ...partialRawRule + }: Partial, + references: SavedObjectReference[] | undefined, + includeLegacyId: boolean = false, + excludeFromPublicApi: boolean = false, + includeSnoozeData: boolean = false +): PartialRule | PartialRuleWithLegacyId { + const snoozeScheduleDates = snoozeSchedule?.map((s) => ({ + ...s, + rRule: { + ...s.rRule, + dtstart: new Date(s.rRule.dtstart), + ...(s.rRule.until ? { until: new Date(s.rRule.until) } : {}), + }, + })); + const includeSnoozeSchedule = + snoozeSchedule !== undefined && !isEmpty(snoozeSchedule) && !excludeFromPublicApi; + const isSnoozedUntil = includeSnoozeSchedule + ? calculateIsSnoozedUntil({ + muteAll: partialRawRule.muteAll ?? false, + snoozeSchedule, + }) + : null; + const includeMonitoring = monitoring && !excludeFromPublicApi; + const rule = { + id, + notifyWhen, + ...omit(partialRawRule, excludeFromPublicApi ? [...context.fieldsToExcludeFromPublicApi] : ''), + // we currently only support the Interval Schedule type + // Once we support additional types, this type signature will likely change + schedule: schedule as IntervalSchedule, + actions: actions ? injectReferencesIntoActions(id, actions, references || []) : [], + params: injectReferencesIntoParams(id, ruleType, params, references || []) as Params, + ...(excludeFromPublicApi ? {} : { snoozeSchedule: snoozeScheduleDates ?? [] }), + ...(includeSnoozeData && !excludeFromPublicApi + ? { + activeSnoozes: getActiveScheduledSnoozes({ + snoozeSchedule, + muteAll: partialRawRule.muteAll ?? false, + })?.map((s) => s.id), + isSnoozedUntil, + } + : {}), + ...(updatedAt ? { updatedAt: new Date(updatedAt) } : {}), + ...(createdAt ? { createdAt: new Date(createdAt) } : {}), + ...(scheduledTaskId ? { scheduledTaskId } : {}), + ...(executionStatus + ? { executionStatus: ruleExecutionStatusFromRaw(context.logger, id, executionStatus) } + : {}), + ...(includeMonitoring + ? { monitoring: convertMonitoringFromRawAndVerify(context.logger, id, monitoring) } + : {}), + ...(nextRun ? { nextRun: new Date(nextRun) } : {}), + }; + + return includeLegacyId + ? ({ ...rule, legacyId } as PartialRuleWithLegacyId) + : (rule as PartialRule); +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/get_authorization_filter.ts b/x-pack/plugins/alerting/server/rules_client/lib/get_authorization_filter.ts new file mode 100644 index 0000000000000..28e42c6b12e42 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/get_authorization_filter.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AlertingAuthorizationEntity } from '../../authorization'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; +import { alertingAuthorizationFilterOpts } from '../common/constants'; +import { BulkAction } from '../types'; + +export const getAuthorizationFilter = async ( + context: RulesClientContext, + { action }: { action: BulkAction } +) => { + try { + const authorizationTuple = await context.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + alertingAuthorizationFilterOpts + ); + return authorizationTuple.filter; + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction[action], + error, + }) + ); + throw error; + } +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/index.ts b/x-pack/plugins/alerting/server/rules_client/lib/index.ts index f7e0620222ec6..1f9534a5c6da2 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/index.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/index.ts @@ -5,11 +5,13 @@ * 2.0. */ -export { mapSortField } from './map_sort_field'; -export { validateOperationOnAttributes } from './validate_attributes'; -export { retryIfBulkEditConflicts } from './retry_if_bulk_edit_conflicts'; -export { retryIfBulkDeleteConflicts } from './retry_if_bulk_delete_conflicts'; -export { retryIfBulkDisableConflicts } from './retry_if_bulk_disable_conflicts'; -export { retryIfBulkOperationConflicts } from './retry_if_bulk_operation_conflicts'; -export { applyBulkEditOperation } from './apply_bulk_edit_operation'; -export { buildKueryNodeFilter } from './build_kuery_node_filter'; +export { createRuleSavedObject } from './create_rule_saved_object'; +export { extractReferences } from './extract_references'; +export { validateActions } from './validate_actions'; +export { updateMeta } from './update_meta'; +export * from './get_alert_from_raw'; +export { getAuthorizationFilter } from './get_authorization_filter'; +export { checkAuthorizationAndGetTotal } from './check_authorization_and_get_total'; +export { scheduleTask } from './schedule_task'; +export { createNewAPIKeySet } from './create_new_api_key_set'; +export { recoverRuleAlerts } from './recover_rule_alerts'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/recover_rule_alerts.ts b/x-pack/plugins/alerting/server/rules_client/lib/recover_rule_alerts.ts new file mode 100644 index 0000000000000..aaa84a8b6950b --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/recover_rule_alerts.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 { mapValues } from 'lodash'; +import { SAVED_OBJECT_REL_PRIMARY } from '@kbn/event-log-plugin/server'; +import { RawRule, SanitizedRule, RawAlertInstance as RawAlert } from '../../types'; +import { taskInstanceToAlertTaskInstance } from '../../task_runner/alert_task_instance'; +import { Alert } from '../../alert'; +import { EVENT_LOG_ACTIONS } from '../../plugin'; +import { createAlertEventLogRecordObject } from '../../lib/create_alert_event_log_record_object'; +import { RulesClientContext } from '../types'; + +export const recoverRuleAlerts = async ( + context: RulesClientContext, + id: string, + attributes: RawRule +) => { + if (!context.eventLogger || !attributes.scheduledTaskId) return; + try { + const { state } = taskInstanceToAlertTaskInstance( + await context.taskManager.get(attributes.scheduledTaskId), + attributes as unknown as SanitizedRule + ); + + const recoveredAlerts = mapValues, Alert>( + state.alertInstances ?? {}, + (rawAlertInstance, alertId) => new Alert(alertId, rawAlertInstance) + ); + const recoveredAlertIds = Object.keys(recoveredAlerts); + + for (const alertId of recoveredAlertIds) { + const { group: actionGroup } = recoveredAlerts[alertId].getLastScheduledActions() ?? {}; + const instanceState = recoveredAlerts[alertId].getState(); + const message = `instance '${alertId}' has recovered due to the rule was disabled`; + + const event = createAlertEventLogRecordObject({ + ruleId: id, + ruleName: attributes.name, + ruleType: context.ruleTypeRegistry.get(attributes.alertTypeId), + consumer: attributes.consumer, + instanceId: alertId, + action: EVENT_LOG_ACTIONS.recoveredInstance, + message, + state: instanceState, + group: actionGroup, + namespace: context.namespace, + spaceId: context.spaceId, + savedObjects: [ + { + id, + type: 'alert', + typeId: attributes.alertTypeId, + relation: SAVED_OBJECT_REL_PRIMARY, + }, + ], + }); + context.eventLogger.logEvent(event); + } + } catch (error) { + // this should not block the rest of the disable process + context.logger.warn( + `rulesClient.disable('${id}') - Could not write recovery events - ${error.message}` + ); + } +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.test.ts deleted file mode 100644 index 32a18ea7f0984..0000000000000 --- a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { KueryNode } from '@kbn/es-query'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; - -import { retryIfBulkDeleteConflicts } from './retry_if_bulk_delete_conflicts'; -import { RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; - -const mockFilter: KueryNode = { - type: 'function', - value: 'mock', -}; - -const mockLogger = loggingSystemMock.create().get(); - -const mockSuccessfulResult = { - apiKeysToInvalidate: ['apiKey1'], - errors: [], - taskIdsToDelete: ['taskId1'], -}; - -const error409 = { - message: 'some fake message', - status: 409, - rule: { - id: 'fake_rule_id', - name: 'fake rule name', - }, -}; - -const getOperationConflictsTimes = (times: number) => { - return async () => { - conflictOperationMock(); - times--; - if (times >= 0) { - return { - apiKeysToInvalidate: [], - taskIdsToDelete: [], - errors: [error409], - }; - } - return mockSuccessfulResult; - }; -}; - -const OperationSuccessful = async () => mockSuccessfulResult; -const conflictOperationMock = jest.fn(); - -describe('retryIfBulkDeleteConflicts', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - test('should work when operation is successful', async () => { - const result = await retryIfBulkDeleteConflicts(mockLogger, OperationSuccessful, mockFilter); - - expect(result).toEqual(mockSuccessfulResult); - }); - - test('should throw error when operation fails', async () => { - await expect( - retryIfBulkDeleteConflicts( - mockLogger, - async () => { - throw Error('Test failure'); - }, - mockFilter - ) - ).rejects.toThrowError('Test failure'); - }); - - test(`should return conflict errors when number of retries exceeds ${RETRY_IF_CONFLICTS_ATTEMPTS}`, async () => { - const result = await retryIfBulkDeleteConflicts( - mockLogger, - getOperationConflictsTimes(RETRY_IF_CONFLICTS_ATTEMPTS + 1), - mockFilter - ); - - expect(result.errors).toEqual([error409]); - expect(mockLogger.warn).toBeCalledWith('Bulk delete rules conflicts, exceeded retries'); - }); - - for (let i = 1; i <= RETRY_IF_CONFLICTS_ATTEMPTS; i++) { - test(`should work when operation conflicts ${i} times`, async () => { - const result = await retryIfBulkDeleteConflicts( - mockLogger, - getOperationConflictsTimes(i), - mockFilter - ); - - expect(conflictOperationMock.mock.calls.length).toBe(i + 1); - expect(result).toStrictEqual(mockSuccessfulResult); - }); - } -}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.ts deleted file mode 100644 index 0c2bac9695c85..0000000000000 --- a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.ts +++ /dev/null @@ -1,130 +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 pMap from 'p-map'; -import { chunk } from 'lodash'; -import { KueryNode } from '@kbn/es-query'; -import { Logger } from '@kbn/core/server'; -import { convertRuleIdsToKueryNode } from '../../lib'; -import { BulkOperationError } from '../rules_client'; -import { waitBeforeNextRetry, RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; - -const MAX_RULES_IDS_IN_RETRY = 1000; - -export type BulkDeleteOperation = (filter: KueryNode | null) => Promise<{ - apiKeysToInvalidate: string[]; - errors: BulkOperationError[]; - taskIdsToDelete: string[]; -}>; - -interface ReturnRetry { - apiKeysToInvalidate: string[]; - errors: BulkOperationError[]; - taskIdsToDelete: string[]; -} - -/** - * Retries BulkDelete requests - * If in response are presents conflicted savedObjects(409 statusCode), this util constructs filter with failed SO ids and retries bulkDelete operation until - * all SO updated or number of retries exceeded - * @param logger - * @param bulkEditOperation - * @param filter - KueryNode filter - * @param retries - number of retries left - * @param accApiKeysToInvalidate - accumulated apiKeys that need to be invalidated - * @param accErrors - accumulated conflict errors - * @param accTaskIdsToDelete - accumulated task ids - * @returns Promise - */ -export const retryIfBulkDeleteConflicts = async ( - logger: Logger, - bulkDeleteOperation: BulkDeleteOperation, - filter: KueryNode | null, - retries: number = RETRY_IF_CONFLICTS_ATTEMPTS, - accApiKeysToInvalidate: string[] = [], - accErrors: BulkOperationError[] = [], - accTaskIdsToDelete: string[] = [] -): Promise => { - try { - const { - apiKeysToInvalidate: currentApiKeysToInvalidate, - errors: currentErrors, - taskIdsToDelete: currentTaskIdsToDelete, - } = await bulkDeleteOperation(filter); - - const apiKeysToInvalidate = [...accApiKeysToInvalidate, ...currentApiKeysToInvalidate]; - const taskIdsToDelete = [...accTaskIdsToDelete, ...currentTaskIdsToDelete]; - const errors = - retries <= 0 - ? [...accErrors, ...currentErrors] - : [...accErrors, ...currentErrors.filter((error) => error.status !== 409)]; - - const ruleIdsWithConflictError = currentErrors.reduce((acc, error) => { - if (error.status === 409) { - return [...acc, error.rule.id]; - } - return acc; - }, []); - - if (ruleIdsWithConflictError.length === 0) { - return { - apiKeysToInvalidate, - errors, - taskIdsToDelete, - }; - } - - if (retries <= 0) { - logger.warn('Bulk delete rules conflicts, exceeded retries'); - - return { - apiKeysToInvalidate, - errors, - taskIdsToDelete, - }; - } - - logger.debug( - `Bulk delete rules conflicts, retrying ..., ${ruleIdsWithConflictError.length} saved objects conflicted` - ); - - await waitBeforeNextRetry(retries); - - // here, we construct filter query with ids. But, due to a fact that number of conflicted saved objects can exceed few thousands we can encounter following error: - // "all shards failed: search_phase_execution_exception: [query_shard_exception] Reason: failed to create query: maxClauseCount is set to 2621" - // That's why we chunk processing ids into pieces by size equals to MAX_RULES_IDS_IN_RETRY - return ( - await pMap( - chunk(ruleIdsWithConflictError, MAX_RULES_IDS_IN_RETRY), - async (queryIds) => - retryIfBulkDeleteConflicts( - logger, - bulkDeleteOperation, - convertRuleIdsToKueryNode(queryIds), - retries - 1, - apiKeysToInvalidate, - errors, - taskIdsToDelete - ), - { - concurrency: 1, - } - ) - ).reduce( - (acc, item) => { - return { - apiKeysToInvalidate: [...acc.apiKeysToInvalidate, ...item.apiKeysToInvalidate], - errors: [...acc.errors, ...item.errors], - taskIdsToDelete: [...acc.taskIdsToDelete, ...item.taskIdsToDelete], - }; - }, - { apiKeysToInvalidate: [], errors: [], taskIdsToDelete: [] } - ); - } catch (err) { - throw err; - } -}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/schedule_task.ts b/x-pack/plugins/alerting/server/rules_client/lib/schedule_task.ts new file mode 100644 index 0000000000000..eecdcf0314d02 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/schedule_task.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RulesClientContext } from '../types'; +import { ScheduleTaskOptions } from '../types'; + +export async function scheduleTask(context: RulesClientContext, opts: ScheduleTaskOptions) { + const { id, consumer, ruleTypeId, schedule, throwOnConflict } = opts; + const taskInstance = { + id, // use the same ID for task document as the rule + taskType: `alerting:${ruleTypeId}`, + schedule, + params: { + alertId: id, + spaceId: context.spaceId, + consumer, + }, + state: { + previousStartedAt: null, + alertTypeState: {}, + alertInstances: {}, + }, + scope: ['alerting'], + enabled: true, + }; + try { + return await context.taskManager.schedule(taskInstance); + } catch (err) { + if (err.statusCode === 409 && !throwOnConflict) { + return taskInstance; + } + throw err; + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/update_meta.ts b/x-pack/plugins/alerting/server/rules_client/lib/update_meta.ts new file mode 100644 index 0000000000000..5fbe2b275f077 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/update_meta.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RawRule } from '../../types'; +import { RulesClientContext } from '../types'; + +export function updateMeta>( + context: RulesClientContext, + alertAttributes: T +): T { + if (alertAttributes.hasOwnProperty('apiKey') || alertAttributes.hasOwnProperty('apiKeyOwner')) { + alertAttributes.meta = alertAttributes.meta ?? {}; + alertAttributes.meta.versionApiKeyLastmodified = context.kibanaVersion; + } + return alertAttributes; +} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts new file mode 100644 index 0000000000000..683b95c066343 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Boom from '@hapi/boom'; +import { map } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { RawRule } from '../../types'; +import { UntypedNormalizedRuleType } from '../../rule_type_registry'; +import { NormalizedAlertAction } from '../types'; +import { RulesClientContext } from '../types'; + +export async function validateActions( + context: RulesClientContext, + alertType: UntypedNormalizedRuleType, + data: Pick & { actions: NormalizedAlertAction[] } +): Promise { + const { actions, notifyWhen, throttle } = data; + const hasNotifyWhen = typeof notifyWhen !== 'undefined'; + const hasThrottle = typeof throttle !== 'undefined'; + let usesRuleLevelFreqParams; + if (hasNotifyWhen && hasThrottle) usesRuleLevelFreqParams = true; + else if (!hasNotifyWhen && !hasThrottle) usesRuleLevelFreqParams = false; + else { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.usesValidGlobalFreqParams.oneUndefined', { + defaultMessage: + 'Rule-level notifyWhen and throttle must both be defined or both be undefined', + }) + ); + } + + if (actions.length === 0) { + return; + } + + // check for actions using connectors with missing secrets + const actionsClient = await context.getActionsClient(); + const actionIds = [...new Set(actions.map((action) => action.id))]; + const actionResults = (await actionsClient.getBulk(actionIds)) || []; + const actionsUsingConnectorsWithMissingSecrets = actionResults.filter( + (result) => result.isMissingSecrets + ); + + if (actionsUsingConnectorsWithMissingSecrets.length) { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.validateActions.misconfiguredConnector', { + defaultMessage: 'Invalid connectors: {groups}', + values: { + groups: actionsUsingConnectorsWithMissingSecrets + .map((connector) => connector.name) + .join(', '), + }, + }) + ); + } + + // check for actions with invalid action groups + const { actionGroups: alertTypeActionGroups } = alertType; + const usedAlertActionGroups = actions.map((action) => action.group); + const availableAlertTypeActionGroups = new Set(map(alertTypeActionGroups, 'id')); + const invalidActionGroups = usedAlertActionGroups.filter( + (group) => !availableAlertTypeActionGroups.has(group) + ); + if (invalidActionGroups.length) { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.validateActions.invalidGroups', { + defaultMessage: 'Invalid action groups: {groups}', + values: { + groups: invalidActionGroups.join(', '), + }, + }) + ); + } + + // check for actions using frequency params if the rule has rule-level frequency params defined + if (usesRuleLevelFreqParams) { + const actionsWithFrequency = actions.filter((action) => Boolean(action.frequency)); + if (actionsWithFrequency.length) { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.validateActions.mixAndMatchFreqParams', { + defaultMessage: + 'Cannot specify per-action frequency params when notify_when and throttle are defined at the rule level: {groups}', + values: { + groups: actionsWithFrequency.map((a) => a.group).join(', '), + }, + }) + ); + } + } else { + const actionsWithoutFrequency = actions.filter((action) => !action.frequency); + if (actionsWithoutFrequency.length) { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.validateActions.notAllActionsWithFreq', { + defaultMessage: 'Actions missing frequency parameters: {groups}', + values: { + groups: actionsWithoutFrequency.map((a) => a.group).join(', '), + }, + }) + ); + } + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/aggregate.ts b/x-pack/plugins/alerting/server/rules_client/methods/aggregate.ts new file mode 100644 index 0000000000000..79a07b3ebad49 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/aggregate.ts @@ -0,0 +1,223 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KueryNode, nodeBuilder } from '@kbn/es-query'; +import { RawRule, RuleExecutionStatusValues, RuleLastRunOutcomeValues } from '../../types'; +import { AlertingAuthorizationEntity } from '../../authorization'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { buildKueryNodeFilter } from '../common'; +import { alertingAuthorizationFilterOpts } from '../common/constants'; +import { RulesClientContext } from '../types'; + +export interface AggregateOptions extends IndexType { + search?: string; + defaultSearchOperator?: 'AND' | 'OR'; + searchFields?: string[]; + hasReference?: { + type: string; + id: string; + }; + filter?: string | KueryNode; +} + +interface IndexType { + [key: string]: unknown; +} + +export interface AggregateResult { + alertExecutionStatus: { [status: string]: number }; + ruleLastRunOutcome: { [status: string]: number }; + ruleEnabledStatus?: { enabled: number; disabled: number }; + ruleMutedStatus?: { muted: number; unmuted: number }; + ruleSnoozedStatus?: { snoozed: number }; + ruleTags?: string[]; +} + +export interface RuleAggregation { + status: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; + outcome: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; + muted: { + buckets: Array<{ + key: number; + key_as_string: string; + doc_count: number; + }>; + }; + enabled: { + buckets: Array<{ + key: number; + key_as_string: string; + doc_count: number; + }>; + }; + snoozed: { + count: { + doc_count: number; + }; + }; + tags: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; +} + +export async function aggregate( + context: RulesClientContext, + { options: { fields, filter, ...options } = {} }: { options?: AggregateOptions } = {} +): Promise { + let authorizationTuple; + try { + authorizationTuple = await context.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + alertingAuthorizationFilterOpts + ); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.AGGREGATE, + error, + }) + ); + throw error; + } + + const { filter: authorizationFilter } = authorizationTuple; + const filterKueryNode = buildKueryNodeFilter(filter); + + const resp = await context.unsecuredSavedObjectsClient.find({ + ...options, + filter: + authorizationFilter && filterKueryNode + ? nodeBuilder.and([filterKueryNode, authorizationFilter as KueryNode]) + : authorizationFilter, + page: 1, + perPage: 0, + type: 'alert', + aggs: { + status: { + terms: { field: 'alert.attributes.executionStatus.status' }, + }, + outcome: { + terms: { field: 'alert.attributes.lastRun.outcome' }, + }, + enabled: { + terms: { field: 'alert.attributes.enabled' }, + }, + muted: { + terms: { field: 'alert.attributes.muteAll' }, + }, + tags: { + terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, + }, + snoozed: { + nested: { + path: 'alert.attributes.snoozeSchedule', + }, + aggs: { + count: { + filter: { + exists: { + field: 'alert.attributes.snoozeSchedule.duration', + }, + }, + }, + }, + }, + }, + }); + + if (!resp.aggregations) { + // Return a placeholder with all zeroes + const placeholder: AggregateResult = { + alertExecutionStatus: {}, + ruleLastRunOutcome: {}, + ruleEnabledStatus: { + enabled: 0, + disabled: 0, + }, + ruleMutedStatus: { + muted: 0, + unmuted: 0, + }, + ruleSnoozedStatus: { snoozed: 0 }, + }; + + for (const key of RuleExecutionStatusValues) { + placeholder.alertExecutionStatus[key] = 0; + } + + return placeholder; + } + + const alertExecutionStatus = resp.aggregations.status.buckets.map( + ({ key, doc_count: docCount }) => ({ + [key]: docCount, + }) + ); + + const ruleLastRunOutcome = resp.aggregations.outcome.buckets.map( + ({ key, doc_count: docCount }) => ({ + [key]: docCount, + }) + ); + + const ret: AggregateResult = { + alertExecutionStatus: alertExecutionStatus.reduce( + (acc, curr: { [status: string]: number }) => Object.assign(acc, curr), + {} + ), + ruleLastRunOutcome: ruleLastRunOutcome.reduce( + (acc, curr: { [status: string]: number }) => Object.assign(acc, curr), + {} + ), + }; + + // Fill missing keys with zeroes + for (const key of RuleExecutionStatusValues) { + if (!ret.alertExecutionStatus.hasOwnProperty(key)) { + ret.alertExecutionStatus[key] = 0; + } + } + for (const key of RuleLastRunOutcomeValues) { + if (!ret.ruleLastRunOutcome.hasOwnProperty(key)) { + ret.ruleLastRunOutcome[key] = 0; + } + } + + const enabledBuckets = resp.aggregations.enabled.buckets; + ret.ruleEnabledStatus = { + enabled: enabledBuckets.find((bucket) => bucket.key === 1)?.doc_count ?? 0, + disabled: enabledBuckets.find((bucket) => bucket.key === 0)?.doc_count ?? 0, + }; + + const mutedBuckets = resp.aggregations.muted.buckets; + ret.ruleMutedStatus = { + muted: mutedBuckets.find((bucket) => bucket.key === 1)?.doc_count ?? 0, + unmuted: mutedBuckets.find((bucket) => bucket.key === 0)?.doc_count ?? 0, + }; + + ret.ruleSnoozedStatus = { + snoozed: resp.aggregations.snoozed?.count?.doc_count ?? 0, + }; + + const tagsBuckets = resp.aggregations.tags?.buckets || []; + ret.ruleTags = tagsBuckets.map((bucket) => bucket.key); + + return ret; +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_delete.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_delete.ts new file mode 100644 index 0000000000000..6e581ab94a6d5 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_delete.ts @@ -0,0 +1,177 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KueryNode, nodeBuilder } from '@kbn/es-query'; +import { SavedObjectsBulkUpdateObject } from '@kbn/core/server'; +import { RawRule } from '../../types'; +import { convertRuleIdsToKueryNode } from '../../lib'; +import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { getAuthorizationFilter, checkAuthorizationAndGetTotal, getAlertFromRaw } from '../lib'; +import { + retryIfBulkOperationConflicts, + buildKueryNodeFilter, + getAndValidateCommonBulkOptions, +} from '../common'; +import { BulkOptions, BulkOperationError, RulesClientContext } from '../types'; + +export const bulkDeleteRules = async (context: RulesClientContext, options: BulkOptions) => { + const { ids, filter } = getAndValidateCommonBulkOptions(options); + + const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); + const authorizationFilter = await getAuthorizationFilter(context, { action: 'DELETE' }); + + const kueryNodeFilterWithAuth = + authorizationFilter && kueryNodeFilter + ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) + : kueryNodeFilter; + + const { total } = await checkAuthorizationAndGetTotal(context, { + filter: kueryNodeFilterWithAuth, + action: 'DELETE', + }); + + const { rules, errors, accListSpecificForBulkOperation } = await retryIfBulkOperationConflicts({ + action: 'DELETE', + logger: context.logger, + bulkOperation: (filterKueryNode: KueryNode | null) => + bulkDeleteWithOCC(context, { filter: filterKueryNode }), + filter: kueryNodeFilterWithAuth, + }); + + const [apiKeysToInvalidate, taskIdsToDelete] = accListSpecificForBulkOperation; + + const taskIdsFailedToBeDeleted: string[] = []; + const taskIdsSuccessfullyDeleted: string[] = []; + if (taskIdsToDelete.length > 0) { + try { + const resultFromDeletingTasks = await context.taskManager.bulkRemoveIfExist(taskIdsToDelete); + resultFromDeletingTasks?.statuses.forEach((status) => { + if (status.success) { + taskIdsSuccessfullyDeleted.push(status.id); + } else { + taskIdsFailedToBeDeleted.push(status.id); + } + }); + if (taskIdsSuccessfullyDeleted.length) { + context.logger.debug( + `Successfully deleted schedules for underlying tasks: ${taskIdsSuccessfullyDeleted.join( + ', ' + )}` + ); + } + if (taskIdsFailedToBeDeleted.length) { + context.logger.error( + `Failure to delete schedules for underlying tasks: ${taskIdsFailedToBeDeleted.join(', ')}` + ); + } + } catch (error) { + context.logger.error( + `Failure to delete schedules for underlying tasks: ${taskIdsToDelete.join( + ', ' + )}. TaskManager bulkRemoveIfExist failed with Error: ${error.message}` + ); + } + } + + await bulkMarkApiKeysForInvalidation( + { apiKeys: apiKeysToInvalidate }, + context.logger, + context.unsecuredSavedObjectsClient + ); + + const deletedRules = rules.map(({ id, attributes, references }) => { + return getAlertFromRaw( + context, + id, + attributes.alertTypeId as string, + attributes as RawRule, + references, + false + ); + }); + + return { errors, rules: deletedRules, total, taskIdsFailedToBeDeleted }; +}; + +const bulkDeleteWithOCC = async ( + context: RulesClientContext, + { filter }: { filter: KueryNode | null } +) => { + const rulesFinder = + await context.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( + { + filter, + type: 'alert', + perPage: 100, + ...(context.namespace ? { namespaces: [context.namespace] } : undefined), + } + ); + + const rulesToDelete: Array> = []; + const apiKeysToInvalidate: string[] = []; + const taskIdsToDelete: string[] = []; + const errors: BulkOperationError[] = []; + const apiKeyToRuleIdMapping: Record = {}; + const taskIdToRuleIdMapping: Record = {}; + const ruleNameToRuleIdMapping: Record = {}; + + for await (const response of rulesFinder.find()) { + for (const rule of response.saved_objects) { + if (rule.attributes.apiKey) { + apiKeyToRuleIdMapping[rule.id] = rule.attributes.apiKey; + } + if (rule.attributes.name) { + ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; + } + if (rule.attributes.scheduledTaskId) { + taskIdToRuleIdMapping[rule.id] = rule.attributes.scheduledTaskId; + } + rulesToDelete.push(rule); + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DELETE, + outcome: 'unknown', + savedObject: { type: 'alert', id: rule.id }, + }) + ); + } + } + + const result = await context.unsecuredSavedObjectsClient.bulkDelete(rulesToDelete); + + const deletedRuleIds: string[] = []; + + result.statuses.forEach((status) => { + if (status.error === undefined) { + if (apiKeyToRuleIdMapping[status.id]) { + apiKeysToInvalidate.push(apiKeyToRuleIdMapping[status.id]); + } + if (taskIdToRuleIdMapping[status.id]) { + taskIdsToDelete.push(taskIdToRuleIdMapping[status.id]); + } + deletedRuleIds.push(status.id); + } else { + errors.push({ + message: status.error.message ?? 'n/a', + status: status.error.statusCode, + rule: { + id: status.id, + name: ruleNameToRuleIdMapping[status.id] ?? 'n/a', + }, + }); + } + }); + const rules = rulesToDelete.filter((rule) => deletedRuleIds.includes(rule.id)); + + return { + errors, + rules, + accListSpecificForBulkOperation: [apiKeysToInvalidate, taskIdsToDelete], + }; +}; diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts new file mode 100644 index 0000000000000..1a3b12e618fd2 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_disable.ts @@ -0,0 +1,227 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import pMap from 'p-map'; +import { KueryNode, nodeBuilder } from '@kbn/es-query'; +import { SavedObjectsBulkUpdateObject } from '@kbn/core/server'; +import { RawRule } from '../../types'; +import { convertRuleIdsToKueryNode } from '../../lib'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { + retryIfBulkDisableConflicts, + buildKueryNodeFilter, + getAndValidateCommonBulkOptions, +} from '../common'; +import { + getAuthorizationFilter, + checkAuthorizationAndGetTotal, + getAlertFromRaw, + recoverRuleAlerts, + updateMeta, +} from '../lib'; +import { BulkOptions, BulkOperationError, RulesClientContext } from '../types'; + +export const bulkDisableRules = async (context: RulesClientContext, options: BulkOptions) => { + const { ids, filter } = getAndValidateCommonBulkOptions(options); + + const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); + const authorizationFilter = await getAuthorizationFilter(context, { action: 'DISABLE' }); + + const kueryNodeFilterWithAuth = + authorizationFilter && kueryNodeFilter + ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) + : kueryNodeFilter; + + const { total } = await checkAuthorizationAndGetTotal(context, { + filter: kueryNodeFilterWithAuth, + action: 'DISABLE', + }); + + const { errors, rules, taskIdsToDisable, taskIdsToDelete } = await retryIfBulkDisableConflicts( + context.logger, + (filterKueryNode: KueryNode | null) => + bulkDisableRulesWithOCC(context, { filter: filterKueryNode }), + kueryNodeFilterWithAuth + ); + + if (taskIdsToDisable.length > 0) { + try { + const resultFromDisablingTasks = await context.taskManager.bulkDisable(taskIdsToDisable); + if (resultFromDisablingTasks.tasks.length) { + context.logger.debug( + `Successfully disabled schedules for underlying tasks: ${resultFromDisablingTasks.tasks + .map((task) => task.id) + .join(', ')}` + ); + } + if (resultFromDisablingTasks.errors.length) { + context.logger.error( + `Failure to disable schedules for underlying tasks: ${resultFromDisablingTasks.errors + .map((error) => error.task.id) + .join(', ')}` + ); + } + } catch (error) { + context.logger.error( + `Failure to disable schedules for underlying tasks: ${taskIdsToDisable.join( + ', ' + )}. TaskManager bulkDisable failed with Error: ${error.message}` + ); + } + } + + const taskIdsFailedToBeDeleted: string[] = []; + const taskIdsSuccessfullyDeleted: string[] = []; + + if (taskIdsToDelete.length > 0) { + try { + const resultFromDeletingTasks = await context.taskManager.bulkRemoveIfExist(taskIdsToDelete); + resultFromDeletingTasks?.statuses.forEach((status) => { + if (status.success) { + taskIdsSuccessfullyDeleted.push(status.id); + } else { + taskIdsFailedToBeDeleted.push(status.id); + } + }); + if (taskIdsSuccessfullyDeleted.length) { + context.logger.debug( + `Successfully deleted schedules for underlying tasks: ${taskIdsSuccessfullyDeleted.join( + ', ' + )}` + ); + } + if (taskIdsFailedToBeDeleted.length) { + context.logger.error( + `Failure to delete schedules for underlying tasks: ${taskIdsFailedToBeDeleted.join(', ')}` + ); + } + } catch (error) { + context.logger.error( + `Failure to delete schedules for underlying tasks: ${taskIdsToDelete.join( + ', ' + )}. TaskManager bulkRemoveIfExist failed with Error: ${error.message}` + ); + } + } + + const updatedRules = rules.map(({ id, attributes, references }) => { + return getAlertFromRaw( + context, + id, + attributes.alertTypeId as string, + attributes as RawRule, + references, + false + ); + }); + + return { errors, rules: updatedRules, total }; +}; + +const bulkDisableRulesWithOCC = async ( + context: RulesClientContext, + { filter }: { filter: KueryNode | null } +) => { + const rulesFinder = + await context.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( + { + filter, + type: 'alert', + perPage: 100, + ...(context.namespace ? { namespaces: [context.namespace] } : undefined), + } + ); + + const rulesToDisable: Array> = []; + const errors: BulkOperationError[] = []; + const ruleNameToRuleIdMapping: Record = {}; + + for await (const response of rulesFinder.find()) { + await pMap(response.saved_objects, async (rule) => { + try { + if (rule.attributes.enabled === false) return; + + recoverRuleAlerts(context, rule.id, rule.attributes); + + if (rule.attributes.name) { + ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; + } + + const username = await context.getUserName(); + const updatedAttributes = updateMeta(context, { + ...rule.attributes, + enabled: false, + scheduledTaskId: + rule.attributes.scheduledTaskId === rule.id ? rule.attributes.scheduledTaskId : null, + updatedBy: username, + updatedAt: new Date().toISOString(), + }); + + rulesToDisable.push({ + ...rule, + attributes: { + ...updatedAttributes, + }, + }); + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DISABLE, + outcome: 'unknown', + savedObject: { type: 'alert', id: rule.id }, + }) + ); + } catch (error) { + errors.push({ + message: error.message, + rule: { + id: rule.id, + name: rule.attributes?.name, + }, + }); + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DISABLE, + error, + }) + ); + } + }); + } + + const result = await context.unsecuredSavedObjectsClient.bulkCreate(rulesToDisable, { + overwrite: true, + }); + + const taskIdsToDisable: string[] = []; + const taskIdsToDelete: string[] = []; + const disabledRules: Array> = []; + + result.saved_objects.forEach((rule) => { + if (rule.error === undefined) { + if (rule.attributes.scheduledTaskId) { + if (rule.attributes.scheduledTaskId !== rule.id) { + taskIdsToDelete.push(rule.attributes.scheduledTaskId); + } else { + taskIdsToDisable.push(rule.attributes.scheduledTaskId); + } + } + disabledRules.push(rule); + } else { + errors.push({ + message: rule.error.message ?? 'n/a', + status: rule.error.statusCode, + rule: { + id: rule.id, + name: ruleNameToRuleIdMapping[rule.id] ?? 'n/a', + }, + }); + } + }); + + return { errors, rules: disabledRules, taskIdsToDisable, taskIdsToDelete }; +}; diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts new file mode 100644 index 0000000000000..0ac7c8d24d0fe --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_edit.ts @@ -0,0 +1,544 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import pMap from 'p-map'; +import Boom from '@hapi/boom'; +import { cloneDeep } from 'lodash'; +import { AlertConsumers } from '@kbn/rule-data-utils'; +import { KueryNode, nodeBuilder } from '@kbn/es-query'; +import { SavedObjectsBulkUpdateObject, SavedObjectsUpdateResponse } from '@kbn/core/server'; +import { RawRule, SanitizedRule, RuleTypeParams, Rule, RuleSnoozeSchedule } from '../../types'; +import { + validateRuleTypeParams, + getRuleNotifyWhenType, + validateMutatedRuleTypeParams, + convertRuleIdsToKueryNode, +} from '../../lib'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { parseDuration } from '../../../common/parse_duration'; +import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { + retryIfBulkEditConflicts, + applyBulkEditOperation, + buildKueryNodeFilter, + injectReferencesIntoActions, + generateAPIKeyName, + apiKeyAsAlertAttributes, + getBulkSnoozeAttributes, + getBulkUnsnoozeAttributes, + verifySnoozeScheduleLimit, +} from '../common'; +import { + alertingAuthorizationFilterOpts, + MAX_RULES_NUMBER_FOR_BULK_OPERATION, + RULE_TYPE_CHECKS_CONCURRENCY, + API_KEY_GENERATE_CONCURRENCY, +} from '../common/constants'; +import { getMappedParams } from '../common/mapped_params_utils'; +import { getAlertFromRaw, extractReferences, validateActions, updateMeta } from '../lib'; +import { + NormalizedAlertAction, + BulkOperationError, + RuleBulkOperationAggregation, + RulesClientContext, +} from '../types'; + +export type BulkEditFields = keyof Pick< + Rule, + 'actions' | 'tags' | 'schedule' | 'throttle' | 'notifyWhen' | 'snoozeSchedule' | 'apiKey' +>; + +export type BulkEditOperation = + | { + operation: 'add' | 'delete' | 'set'; + field: Extract; + value: string[]; + } + | { + operation: 'add' | 'set'; + field: Extract; + value: NormalizedAlertAction[]; + } + | { + operation: 'set'; + field: Extract; + value: Rule['schedule']; + } + | { + operation: 'set'; + field: Extract; + value: Rule['throttle']; + } + | { + operation: 'set'; + field: Extract; + value: Rule['notifyWhen']; + } + | { + operation: 'set'; + field: Extract; + value: RuleSnoozeSchedule; + } + | { + operation: 'delete'; + field: Extract; + value?: string[]; + } + | { + operation: 'set'; + field: Extract; + value?: undefined; + }; + +type RuleParamsModifier = (params: Params) => Promise; + +export interface BulkEditOptionsFilter { + filter?: string | KueryNode; + operations: BulkEditOperation[]; + paramsModifier?: RuleParamsModifier; +} + +export interface BulkEditOptionsIds { + ids: string[]; + operations: BulkEditOperation[]; + paramsModifier?: RuleParamsModifier; +} + +export type BulkEditOptions = + | BulkEditOptionsFilter + | BulkEditOptionsIds; + +export async function bulkEdit( + context: RulesClientContext, + options: BulkEditOptions +): Promise<{ + rules: Array>; + errors: BulkOperationError[]; + total: number; +}> { + const queryFilter = (options as BulkEditOptionsFilter).filter; + const ids = (options as BulkEditOptionsIds).ids; + + if (ids && queryFilter) { + throw Boom.badRequest( + "Both 'filter' and 'ids' are supplied. Define either 'ids' or 'filter' properties in method arguments" + ); + } + + const qNodeQueryFilter = buildKueryNodeFilter(queryFilter); + + const qNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : qNodeQueryFilter; + let authorizationTuple; + try { + authorizationTuple = await context.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + alertingAuthorizationFilterOpts + ); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.BULK_EDIT, + error, + }) + ); + throw error; + } + const { filter: authorizationFilter } = authorizationTuple; + const qNodeFilterWithAuth = + authorizationFilter && qNodeFilter + ? nodeBuilder.and([qNodeFilter, authorizationFilter as KueryNode]) + : qNodeFilter; + + const { aggregations, total } = await context.unsecuredSavedObjectsClient.find< + RawRule, + RuleBulkOperationAggregation + >({ + filter: qNodeFilterWithAuth, + page: 1, + perPage: 0, + type: 'alert', + aggs: { + alertTypeId: { + multi_terms: { + terms: [ + { field: 'alert.attributes.alertTypeId' }, + { field: 'alert.attributes.consumer' }, + ], + }, + }, + }, + }); + + if (total > MAX_RULES_NUMBER_FOR_BULK_OPERATION) { + throw Boom.badRequest( + `More than ${MAX_RULES_NUMBER_FOR_BULK_OPERATION} rules matched for bulk edit` + ); + } + const buckets = aggregations?.alertTypeId.buckets; + + if (buckets === undefined) { + throw Error('No rules found for bulk edit'); + } + + await pMap( + buckets, + async ({ key: [ruleType, consumer] }) => { + context.ruleTypeRegistry.ensureRuleTypeEnabled(ruleType); + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: ruleType, + consumer, + operation: WriteOperations.BulkEdit, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.BULK_EDIT, + error, + }) + ); + throw error; + } + }, + { concurrency: RULE_TYPE_CHECKS_CONCURRENCY } + ); + + const { apiKeysToInvalidate, results, errors } = await retryIfBulkEditConflicts( + context.logger, + `rulesClient.update('operations=${JSON.stringify(options.operations)}, paramsModifier=${ + options.paramsModifier ? '[Function]' : undefined + }')`, + (filterKueryNode: KueryNode | null) => + bulkEditOcc(context, { + filter: filterKueryNode, + operations: options.operations, + paramsModifier: options.paramsModifier, + }), + qNodeFilterWithAuth + ); + + await bulkMarkApiKeysForInvalidation( + { apiKeys: apiKeysToInvalidate }, + context.logger, + context.unsecuredSavedObjectsClient + ); + + const updatedRules = results.map(({ id, attributes, references }) => { + return getAlertFromRaw( + context, + id, + attributes.alertTypeId as string, + attributes as RawRule, + references, + false + ); + }); + + // update schedules only if schedule operation is present + const scheduleOperation = options.operations.find( + ( + operation + ): operation is Extract }> => + operation.field === 'schedule' + ); + + if (scheduleOperation?.value) { + const taskIds = updatedRules.reduce((acc, rule) => { + if (rule.scheduledTaskId) { + acc.push(rule.scheduledTaskId); + } + return acc; + }, []); + + try { + await context.taskManager.bulkUpdateSchedules(taskIds, scheduleOperation.value); + context.logger.debug( + `Successfully updated schedules for underlying tasks: ${taskIds.join(', ')}` + ); + } catch (error) { + context.logger.error( + `Failure to update schedules for underlying tasks: ${taskIds.join( + ', ' + )}. TaskManager bulkUpdateSchedules failed with Error: ${error.message}` + ); + } + } + + return { rules: updatedRules, errors, total }; +} + +async function bulkEditOcc( + context: RulesClientContext, + { + filter, + operations, + paramsModifier, + }: { + filter: KueryNode | null; + operations: BulkEditOptions['operations']; + paramsModifier: BulkEditOptions['paramsModifier']; + } +): Promise<{ + apiKeysToInvalidate: string[]; + rules: Array>; + resultSavedObjects: Array>; + errors: BulkOperationError[]; +}> { + const rulesFinder = + await context.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( + { + filter, + type: 'alert', + perPage: 100, + ...(context.namespace ? { namespaces: [context.namespace] } : undefined), + } + ); + + const rules: Array> = []; + const errors: BulkOperationError[] = []; + const apiKeysToInvalidate: string[] = []; + const apiKeysMap = new Map(); + const username = await context.getUserName(); + + for await (const response of rulesFinder.find()) { + await pMap( + response.saved_objects, + async (rule) => { + try { + if (rule.attributes.apiKey) { + apiKeysMap.set(rule.id, { oldApiKey: rule.attributes.apiKey }); + } + + const ruleType = context.ruleTypeRegistry.get(rule.attributes.alertTypeId); + + let attributes = cloneDeep(rule.attributes); + let ruleActions = { + actions: injectReferencesIntoActions( + rule.id, + rule.attributes.actions, + rule.references || [] + ), + }; + + for (const operation of operations) { + const { field } = operation; + if (field === 'snoozeSchedule' || field === 'apiKey') { + if (rule.attributes.actions.length) { + try { + await context.actionsAuthorization.ensureAuthorized('execute'); + } catch (error) { + throw Error(`Rule not authorized for bulk ${field} update - ${error.message}`); + } + } + } + } + + let hasUpdateApiKeyOperation = false; + + for (const operation of operations) { + switch (operation.field) { + case 'actions': + await validateActions(context, ruleType, { + ...attributes, + actions: operation.value, + }); + ruleActions = applyBulkEditOperation(operation, ruleActions); + break; + case 'snoozeSchedule': + // Silently skip adding snooze or snooze schedules on security + // rules until we implement snoozing of their rules + if (attributes.consumer === AlertConsumers.SIEM) { + break; + } + if (operation.operation === 'set') { + const snoozeAttributes = getBulkSnoozeAttributes(attributes, operation.value); + try { + verifySnoozeScheduleLimit(snoozeAttributes); + } catch (error) { + throw Error(`Error updating rule: could not add snooze - ${error.message}`); + } + attributes = { + ...attributes, + ...snoozeAttributes, + }; + } + if (operation.operation === 'delete') { + const idsToDelete = operation.value && [...operation.value]; + if (idsToDelete?.length === 0) { + attributes.snoozeSchedule?.forEach((schedule) => { + if (schedule.id) { + idsToDelete.push(schedule.id); + } + }); + } + attributes = { + ...attributes, + ...getBulkUnsnoozeAttributes(attributes, idsToDelete), + }; + } + break; + case 'apiKey': { + hasUpdateApiKeyOperation = true; + break; + } + default: + attributes = applyBulkEditOperation(operation, attributes); + } + } + + // validate schedule interval + if (attributes.schedule.interval) { + const isIntervalInvalid = + parseDuration(attributes.schedule.interval as string) < + context.minimumScheduleIntervalInMs; + if (isIntervalInvalid && context.minimumScheduleInterval.enforce) { + throw Error( + `Error updating rule: the interval is less than the allowed minimum interval of ${context.minimumScheduleInterval.value}` + ); + } else if (isIntervalInvalid && !context.minimumScheduleInterval.enforce) { + context.logger.warn( + `Rule schedule interval (${attributes.schedule.interval}) for "${ruleType.id}" rule type with ID "${attributes.id}" is less than the minimum value (${context.minimumScheduleInterval.value}). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent such changes.` + ); + } + } + + const ruleParams = paramsModifier + ? await paramsModifier(attributes.params as Params) + : attributes.params; + + // validate rule params + const validatedAlertTypeParams = validateRuleTypeParams( + ruleParams, + ruleType.validate?.params + ); + const validatedMutatedAlertTypeParams = validateMutatedRuleTypeParams( + validatedAlertTypeParams, + rule.attributes.params, + ruleType.validate?.params + ); + + const { + actions: rawAlertActions, + references, + params: updatedParams, + } = await extractReferences( + context, + ruleType, + ruleActions.actions, + validatedMutatedAlertTypeParams + ); + + const shouldUpdateApiKey = attributes.enabled || hasUpdateApiKeyOperation; + + // create API key + let createdAPIKey = null; + try { + createdAPIKey = shouldUpdateApiKey + ? await context.createAPIKey(generateAPIKeyName(ruleType.id, attributes.name)) + : null; + } catch (error) { + throw Error(`Error updating rule: could not create API key - ${error.message}`); + } + + const apiKeyAttributes = apiKeyAsAlertAttributes(createdAPIKey, username); + + // collect generated API keys + if (apiKeyAttributes.apiKey) { + apiKeysMap.set(rule.id, { + ...apiKeysMap.get(rule.id), + newApiKey: apiKeyAttributes.apiKey, + }); + } + + // get notifyWhen + const notifyWhen = getRuleNotifyWhenType( + attributes.notifyWhen ?? null, + attributes.throttle ?? null + ); + + const updatedAttributes = updateMeta(context, { + ...attributes, + ...apiKeyAttributes, + params: updatedParams as RawRule['params'], + actions: rawAlertActions, + notifyWhen, + updatedBy: username, + updatedAt: new Date().toISOString(), + }); + + // add mapped_params + const mappedParams = getMappedParams(updatedParams); + + if (Object.keys(mappedParams).length) { + updatedAttributes.mapped_params = mappedParams; + } + + rules.push({ + ...rule, + references, + attributes: updatedAttributes, + }); + } catch (error) { + errors.push({ + message: error.message, + rule: { + id: rule.id, + name: rule.attributes?.name, + }, + }); + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.BULK_EDIT, + error, + }) + ); + } + }, + { concurrency: API_KEY_GENERATE_CONCURRENCY } + ); + } + + let result; + try { + result = await context.unsecuredSavedObjectsClient.bulkCreate(rules, { overwrite: true }); + } catch (e) { + // avoid unused newly generated API keys + if (apiKeysMap.size > 0) { + await bulkMarkApiKeysForInvalidation( + { + apiKeys: Array.from(apiKeysMap.values()).reduce((acc, value) => { + if (value.newApiKey) { + acc.push(value.newApiKey); + } + return acc; + }, []), + }, + context.logger, + context.unsecuredSavedObjectsClient + ); + } + throw e; + } + + result.saved_objects.map(({ id, error }) => { + const oldApiKey = apiKeysMap.get(id)?.oldApiKey; + const newApiKey = apiKeysMap.get(id)?.newApiKey; + + // if SO wasn't saved and has new API key it will be invalidated + if (error && newApiKey) { + apiKeysToInvalidate.push(newApiKey); + // if SO saved and has old Api Key it will be invalidate + } else if (!error && oldApiKey) { + apiKeysToInvalidate.push(oldApiKey); + } + }); + + return { apiKeysToInvalidate, resultSavedObjects: result.saved_objects, errors, rules }; +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts b/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts new file mode 100644 index 0000000000000..394f7aad0dea7 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/bulk_enable.ts @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import pMap from 'p-map'; +import { KueryNode, nodeBuilder } from '@kbn/es-query'; +import { SavedObjectsBulkUpdateObject } from '@kbn/core/server'; +import { RawRule, IntervalSchedule } from '../../types'; +import { convertRuleIdsToKueryNode } from '../../lib'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { + retryIfBulkOperationConflicts, + buildKueryNodeFilter, + getAndValidateCommonBulkOptions, +} from '../common'; +import { + getAuthorizationFilter, + checkAuthorizationAndGetTotal, + getAlertFromRaw, + scheduleTask, + updateMeta, + createNewAPIKeySet, +} from '../lib'; +import { RulesClientContext, BulkOperationError, BulkOptions } from '../types'; + +const getShouldScheduleTask = async ( + context: RulesClientContext, + scheduledTaskId: string | null | undefined +) => { + if (!scheduledTaskId) return true; + try { + // make sure scheduledTaskId exist + await context.taskManager.get(scheduledTaskId); + return false; + } catch (err) { + return true; + } +}; + +export const bulkEnableRules = async (context: RulesClientContext, options: BulkOptions) => { + const { ids, filter } = getAndValidateCommonBulkOptions(options); + + const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); + const authorizationFilter = await getAuthorizationFilter(context, { action: 'ENABLE' }); + + const kueryNodeFilterWithAuth = + authorizationFilter && kueryNodeFilter + ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) + : kueryNodeFilter; + + const { total } = await checkAuthorizationAndGetTotal(context, { + filter: kueryNodeFilterWithAuth, + action: 'ENABLE', + }); + + const { errors, rules, accListSpecificForBulkOperation } = await retryIfBulkOperationConflicts({ + action: 'ENABLE', + logger: context.logger, + bulkOperation: (filterKueryNode: KueryNode | null) => + bulkEnableRulesWithOCC(context, { filter: filterKueryNode }), + filter: kueryNodeFilterWithAuth, + }); + + const [taskIdsToEnable] = accListSpecificForBulkOperation; + + const taskIdsFailedToBeEnabled: string[] = []; + if (taskIdsToEnable.length > 0) { + try { + const resultFromEnablingTasks = await context.taskManager.bulkEnable(taskIdsToEnable); + resultFromEnablingTasks?.errors?.forEach((error) => { + taskIdsFailedToBeEnabled.push(error.task.id); + }); + if (resultFromEnablingTasks.tasks.length) { + context.logger.debug( + `Successfully enabled schedules for underlying tasks: ${resultFromEnablingTasks.tasks + .map((task) => task.id) + .join(', ')}` + ); + } + if (resultFromEnablingTasks.errors.length) { + context.logger.error( + `Failure to enable schedules for underlying tasks: ${resultFromEnablingTasks.errors + .map((error) => error.task.id) + .join(', ')}` + ); + } + } catch (error) { + taskIdsFailedToBeEnabled.push(...taskIdsToEnable); + context.logger.error( + `Failure to enable schedules for underlying tasks: ${taskIdsToEnable.join( + ', ' + )}. TaskManager bulkEnable failed with Error: ${error.message}` + ); + } + } + + const updatedRules = rules.map(({ id, attributes, references }) => { + return getAlertFromRaw( + context, + id, + attributes.alertTypeId as string, + attributes as RawRule, + references, + false + ); + }); + + return { errors, rules: updatedRules, total, taskIdsFailedToBeEnabled }; +}; + +const bulkEnableRulesWithOCC = async ( + context: RulesClientContext, + { filter }: { filter: KueryNode | null } +) => { + const rulesFinder = + await context.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( + { + filter, + type: 'alert', + perPage: 100, + ...(context.namespace ? { namespaces: [context.namespace] } : undefined), + } + ); + + const rulesToEnable: Array> = []; + const taskIdsToEnable: string[] = []; + const errors: BulkOperationError[] = []; + const ruleNameToRuleIdMapping: Record = {}; + + for await (const response of rulesFinder.find()) { + await pMap(response.saved_objects, async (rule) => { + try { + if (rule.attributes.actions.length) { + try { + await context.actionsAuthorization.ensureAuthorized('execute'); + } catch (error) { + throw Error(`Rule not authorized for bulk enable - ${error.message}`); + } + } + if (rule.attributes.enabled === true) return; + if (rule.attributes.name) { + ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; + } + + const username = await context.getUserName(); + + const updatedAttributes = updateMeta(context, { + ...rule.attributes, + ...(!rule.attributes.apiKey && + (await createNewAPIKeySet(context, { attributes: rule.attributes, username }))), + enabled: true, + updatedBy: username, + updatedAt: new Date().toISOString(), + executionStatus: { + status: 'pending', + lastDuration: 0, + lastExecutionDate: new Date().toISOString(), + error: null, + warning: null, + }, + }); + + const shouldScheduleTask = await getShouldScheduleTask( + context, + rule.attributes.scheduledTaskId + ); + + let scheduledTaskId; + if (shouldScheduleTask) { + const scheduledTask = await scheduleTask(context, { + id: rule.id, + consumer: rule.attributes.consumer, + ruleTypeId: rule.attributes.alertTypeId, + schedule: rule.attributes.schedule as IntervalSchedule, + throwOnConflict: false, + }); + scheduledTaskId = scheduledTask.id; + } + + rulesToEnable.push({ + ...rule, + attributes: { + ...updatedAttributes, + ...(scheduledTaskId ? { scheduledTaskId } : undefined), + }, + }); + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.ENABLE, + outcome: 'unknown', + savedObject: { type: 'alert', id: rule.id }, + }) + ); + } catch (error) { + errors.push({ + message: error.message, + rule: { + id: rule.id, + name: rule.attributes?.name, + }, + }); + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.ENABLE, + error, + }) + ); + } + }); + } + + const result = await context.unsecuredSavedObjectsClient.bulkCreate(rulesToEnable, { + overwrite: true, + }); + + const rules: Array> = []; + + result.saved_objects.forEach((rule) => { + if (rule.error === undefined) { + if (rule.attributes.scheduledTaskId) { + taskIdsToEnable.push(rule.attributes.scheduledTaskId); + } + rules.push(rule); + } else { + errors.push({ + message: rule.error.message ?? 'n/a', + status: rule.error.statusCode, + rule: { + id: rule.id, + name: ruleNameToRuleIdMapping[rule.id] ?? 'n/a', + }, + }); + } + }); + return { errors, rules, accListSpecificForBulkOperation: [taskIdsToEnable] }; +}; diff --git a/x-pack/plugins/alerting/server/rules_client/methods/clear_expired_snoozes.ts b/x-pack/plugins/alerting/server/rules_client/methods/clear_expired_snoozes.ts new file mode 100644 index 0000000000000..284e5c89e25f5 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/clear_expired_snoozes.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RawRule } from '../../types'; +import { partiallyUpdateAlert } from '../../saved_objects'; +import { isSnoozeExpired } from '../../lib'; +import { RulesClientContext } from '../types'; +import { updateMeta } from '../lib'; + +export async function clearExpiredSnoozes( + context: RulesClientContext, + { id }: { id: string } +): Promise { + const { attributes, version } = await context.unsecuredSavedObjectsClient.get( + 'alert', + id + ); + + const snoozeSchedule = attributes.snoozeSchedule + ? attributes.snoozeSchedule.filter((s) => { + try { + return !isSnoozeExpired(s); + } catch (e) { + context.logger.error(`Error checking for expiration of snooze ${s.id}: ${e}`); + return true; + } + }) + : []; + + if (snoozeSchedule.length === attributes.snoozeSchedule?.length) return; + + const updateAttributes = updateMeta(context, { + snoozeSchedule, + updatedBy: await context.getUserName(), + updatedAt: new Date().toISOString(), + }); + const updateOptions = { version }; + + await partiallyUpdateAlert( + context.unsecuredSavedObjectsClient, + id, + updateAttributes, + updateOptions + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/clone.ts b/x-pack/plugins/alerting/server/rules_client/methods/clone.ts new file mode 100644 index 0000000000000..b4ebe5891885c --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/clone.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 Semver from 'semver'; +import Boom from '@hapi/boom'; +import { AlertConsumers } from '@kbn/rule-data-utils'; +import { SavedObject, SavedObjectsUtils } from '@kbn/core/server'; +import { RawRule, SanitizedRule, RuleTypeParams } from '../../types'; +import { getDefaultMonitoring } from '../../lib'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { parseDuration } from '../../../common/parse_duration'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { getRuleExecutionStatusPending } from '../../lib/rule_execution_status'; +import { isDetectionEngineAADRuleType } from '../../saved_objects/migrations/utils'; +import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common'; +import { createRuleSavedObject } from '../lib'; +import { RulesClientContext } from '../types'; + +export type CloneArguments = [string, { newId?: string }]; + +export async function clone( + context: RulesClientContext, + id: string, + { newId }: { newId?: string } +): Promise> { + let ruleSavedObject: SavedObject; + + try { + ruleSavedObject = await context.encryptedSavedObjectsClient.getDecryptedAsInternalUser( + 'alert', + id, + { + namespace: context.namespace, + } + ); + } catch (e) { + // We'll skip invalidating the API key since we failed to load the decrypted saved object + context.logger.error( + `update(): Failed to load API key to invalidate on alert ${id}: ${e.message}` + ); + // Still attempt to load the object using SOC + ruleSavedObject = await context.unsecuredSavedObjectsClient.get('alert', id); + } + + /* + * As the time of the creation of this PR, security solution already have a clone/duplicate API + * with some specific business logic so to avoid weird bugs, I prefer to exclude them from this + * functionality until we resolve our difference + */ + if ( + isDetectionEngineAADRuleType(ruleSavedObject) || + ruleSavedObject.attributes.consumer === AlertConsumers.SIEM + ) { + throw Boom.badRequest( + 'The clone functionality is not enable for rule who belongs to security solution' + ); + } + const ruleName = + ruleSavedObject.attributes.name.indexOf('[Clone]') > 0 + ? ruleSavedObject.attributes.name + : `${ruleSavedObject.attributes.name} [Clone]`; + const ruleId = newId ?? SavedObjectsUtils.generateId(); + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: ruleSavedObject.attributes.alertTypeId, + consumer: ruleSavedObject.attributes.consumer, + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.CREATE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.ruleTypeRegistry.ensureRuleTypeEnabled(ruleSavedObject.attributes.alertTypeId); + // Throws an error if alert type isn't registered + const ruleType = context.ruleTypeRegistry.get(ruleSavedObject.attributes.alertTypeId); + const username = await context.getUserName(); + const createTime = Date.now(); + const lastRunTimestamp = new Date(); + const legacyId = Semver.lt(context.kibanaVersion, '8.0.0') ? id : null; + let createdAPIKey = null; + try { + createdAPIKey = ruleSavedObject.attributes.enabled + ? await context.createAPIKey(generateAPIKeyName(ruleType.id, ruleName)) + : null; + } catch (error) { + throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`); + } + const rawRule: RawRule = { + ...ruleSavedObject.attributes, + name: ruleName, + ...apiKeyAsAlertAttributes(createdAPIKey, username), + legacyId, + createdBy: username, + updatedBy: username, + createdAt: new Date(createTime).toISOString(), + updatedAt: new Date(createTime).toISOString(), + snoozeSchedule: [], + muteAll: false, + mutedInstanceIds: [], + executionStatus: getRuleExecutionStatusPending(lastRunTimestamp.toISOString()), + monitoring: getDefaultMonitoring(lastRunTimestamp.toISOString()), + scheduledTaskId: null, + }; + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.CREATE, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + return await createRuleSavedObject(context, { + intervalInMs: parseDuration(rawRule.schedule.interval), + rawRule, + references: ruleSavedObject.references, + ruleId, + }); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/create.ts b/x-pack/plugins/alerting/server/rules_client/methods/create.ts new file mode 100644 index 0000000000000..31707726b4e24 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/create.ts @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import Semver from 'semver'; +import Boom from '@hapi/boom'; +import { SavedObjectsUtils } from '@kbn/core/server'; +import { parseDuration } from '../../../common/parse_duration'; +import { RawRule, SanitizedRule, RuleTypeParams, RuleAction, Rule } from '../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { validateRuleTypeParams, getRuleNotifyWhenType, getDefaultMonitoring } from '../../lib'; +import { getRuleExecutionStatusPending } from '../../lib/rule_execution_status'; +import { createRuleSavedObject, extractReferences, validateActions } from '../lib'; +import { generateAPIKeyName, getMappedParams, apiKeyAsAlertAttributes } from '../common'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; + +type NormalizedAlertAction = Omit; +interface SavedObjectOptions { + id?: string; + migrationVersion?: Record; +} + +export interface CreateOptions { + data: Omit< + Rule, + | 'id' + | 'createdBy' + | 'updatedBy' + | 'createdAt' + | 'updatedAt' + | 'apiKey' + | 'apiKeyOwner' + | 'muteAll' + | 'mutedInstanceIds' + | 'actions' + | 'executionStatus' + | 'snoozeSchedule' + | 'isSnoozedUntil' + | 'lastRun' + | 'nextRun' + > & { actions: NormalizedAlertAction[] }; + options?: SavedObjectOptions; +} + +export async function create( + context: RulesClientContext, + { data, options }: CreateOptions +): Promise> { + const id = options?.id || SavedObjectsUtils.generateId(); + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: data.alertTypeId, + consumer: data.consumer, + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.CREATE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.ruleTypeRegistry.ensureRuleTypeEnabled(data.alertTypeId); + + // Throws an error if alert type isn't registered + const ruleType = context.ruleTypeRegistry.get(data.alertTypeId); + + const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate?.params); + const username = await context.getUserName(); + + let createdAPIKey = null; + try { + createdAPIKey = data.enabled + ? await context.createAPIKey(generateAPIKeyName(ruleType.id, data.name)) + : null; + } catch (error) { + throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`); + } + + await validateActions(context, ruleType, data); + + // Throw error if schedule interval is less than the minimum and we are enforcing it + const intervalInMs = parseDuration(data.schedule.interval); + if ( + intervalInMs < context.minimumScheduleIntervalInMs && + context.minimumScheduleInterval.enforce + ) { + throw Boom.badRequest( + `Error creating rule: the interval is less than the allowed minimum interval of ${context.minimumScheduleInterval.value}` + ); + } + + // Extract saved object references for this rule + const { + references, + params: updatedParams, + actions, + } = await extractReferences(context, ruleType, data.actions, validatedAlertTypeParams); + + const createTime = Date.now(); + const lastRunTimestamp = new Date(); + const legacyId = Semver.lt(context.kibanaVersion, '8.0.0') ? id : null; + const notifyWhen = getRuleNotifyWhenType(data.notifyWhen ?? null, data.throttle ?? null); + const throttle = data.throttle ?? null; + + const rawRule: RawRule = { + ...data, + ...apiKeyAsAlertAttributes(createdAPIKey, username), + legacyId, + actions, + createdBy: username, + updatedBy: username, + createdAt: new Date(createTime).toISOString(), + updatedAt: new Date(createTime).toISOString(), + snoozeSchedule: [], + params: updatedParams as RawRule['params'], + muteAll: false, + mutedInstanceIds: [], + notifyWhen, + throttle, + executionStatus: getRuleExecutionStatusPending(lastRunTimestamp.toISOString()), + monitoring: getDefaultMonitoring(lastRunTimestamp.toISOString()), + }; + + const mappedParams = getMappedParams(updatedParams); + + if (Object.keys(mappedParams).length) { + rawRule.mapped_params = mappedParams; + } + + return await createRuleSavedObject(context, { + intervalInMs, + rawRule, + references, + ruleId: id, + options, + }); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/delete.ts b/x-pack/plugins/alerting/server/rules_client/methods/delete.ts new file mode 100644 index 0000000000000..406184e8f013e --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/delete.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RawRule } from '../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; + +export async function deleteRule(context: RulesClientContext, { id }: { id: string }) { + return await retryIfConflicts( + context.logger, + `rulesClient.delete('${id}')`, + async () => await deleteWithOCC(context, { id }) + ); +} + +async function deleteWithOCC(context: RulesClientContext, { id }: { id: string }) { + let taskIdToRemove: string | undefined | null; + let apiKeyToInvalidate: string | null = null; + let attributes: RawRule; + + try { + const decryptedAlert = + await context.encryptedSavedObjectsClient.getDecryptedAsInternalUser('alert', id, { + namespace: context.namespace, + }); + apiKeyToInvalidate = decryptedAlert.attributes.apiKey; + taskIdToRemove = decryptedAlert.attributes.scheduledTaskId; + attributes = decryptedAlert.attributes; + } catch (e) { + // We'll skip invalidating the API key since we failed to load the decrypted saved object + context.logger.error( + `delete(): Failed to load API key to invalidate on alert ${id}: ${e.message}` + ); + // Still attempt to load the scheduledTaskId using SOC + const alert = await context.unsecuredSavedObjectsClient.get('alert', id); + taskIdToRemove = alert.attributes.scheduledTaskId; + attributes = alert.attributes; + } + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.Delete, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DELETE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DELETE, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + const removeResult = await context.unsecuredSavedObjectsClient.delete('alert', id); + + await Promise.all([ + taskIdToRemove ? context.taskManager.removeIfExists(taskIdToRemove) : null, + apiKeyToInvalidate + ? bulkMarkApiKeysForInvalidation( + { apiKeys: [apiKeyToInvalidate] }, + context.logger, + context.unsecuredSavedObjectsClient + ) + : null, + ]); + + return removeResult; +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/disable.ts b/x-pack/plugins/alerting/server/rules_client/methods/disable.ts new file mode 100644 index 0000000000000..3eae1d2df7b5d --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/disable.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RawRule } from '../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; +import { recoverRuleAlerts, updateMeta } from '../lib'; + +export async function disable(context: RulesClientContext, { id }: { id: string }): Promise { + return await retryIfConflicts( + context.logger, + `rulesClient.disable('${id}')`, + async () => await disableWithOCC(context, { id }) + ); +} + +async function disableWithOCC(context: RulesClientContext, { id }: { id: string }) { + let attributes: RawRule; + let version: string | undefined; + + try { + const decryptedAlert = + await context.encryptedSavedObjectsClient.getDecryptedAsInternalUser('alert', id, { + namespace: context.namespace, + }); + attributes = decryptedAlert.attributes; + version = decryptedAlert.version; + } catch (e) { + context.logger.error(`disable(): Failed to load API key of alert ${id}: ${e.message}`); + // Still attempt to load the attributes and version using SOC + const alert = await context.unsecuredSavedObjectsClient.get('alert', id); + attributes = alert.attributes; + version = alert.version; + } + + recoverRuleAlerts(context, id, attributes); + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.Disable, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DISABLE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DISABLE, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); + + if (attributes.enabled === true) { + await context.unsecuredSavedObjectsClient.update( + 'alert', + id, + updateMeta(context, { + ...attributes, + enabled: false, + scheduledTaskId: attributes.scheduledTaskId === id ? attributes.scheduledTaskId : null, + updatedBy: await context.getUserName(), + updatedAt: new Date().toISOString(), + nextRun: null, + }), + { version } + ); + + // If the scheduledTaskId does not match the rule id, we should + // remove the task, otherwise mark the task as disabled + if (attributes.scheduledTaskId) { + if (attributes.scheduledTaskId !== id) { + await context.taskManager.removeIfExists(attributes.scheduledTaskId); + } else { + await context.taskManager.bulkDisable([attributes.scheduledTaskId]); + } + } + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/enable.ts b/x-pack/plugins/alerting/server/rules_client/methods/enable.ts new file mode 100644 index 0000000000000..5b26061120b0a --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/enable.ts @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RawRule, IntervalSchedule } from '../../types'; +import { updateMonitoring, getNextRun } from '../../lib'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; +import { updateMeta, createNewAPIKeySet, scheduleTask } from '../lib'; + +export async function enable(context: RulesClientContext, { id }: { id: string }): Promise { + return await retryIfConflicts( + context.logger, + `rulesClient.enable('${id}')`, + async () => await enableWithOCC(context, { id }) + ); +} + +async function enableWithOCC(context: RulesClientContext, { id }: { id: string }) { + let existingApiKey: string | null = null; + let attributes: RawRule; + let version: string | undefined; + + try { + const decryptedAlert = + await context.encryptedSavedObjectsClient.getDecryptedAsInternalUser('alert', id, { + namespace: context.namespace, + }); + existingApiKey = decryptedAlert.attributes.apiKey; + attributes = decryptedAlert.attributes; + version = decryptedAlert.version; + } catch (e) { + context.logger.error(`enable(): Failed to load API key of alert ${id}: ${e.message}`); + // Still attempt to load the attributes and version using SOC + const alert = await context.unsecuredSavedObjectsClient.get('alert', id); + attributes = alert.attributes; + version = alert.version; + } + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.Enable, + entity: AlertingAuthorizationEntity.Rule, + }); + + if (attributes.actions.length) { + await context.actionsAuthorization.ensureAuthorized('execute'); + } + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.ENABLE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.ENABLE, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); + + if (attributes.enabled === false) { + const username = await context.getUserName(); + const now = new Date(); + + const schedule = attributes.schedule as IntervalSchedule; + + const updateAttributes = updateMeta(context, { + ...attributes, + ...(!existingApiKey && (await createNewAPIKeySet(context, { attributes, username }))), + ...(attributes.monitoring && { + monitoring: updateMonitoring({ + monitoring: attributes.monitoring, + timestamp: now.toISOString(), + duration: 0, + }), + }), + nextRun: getNextRun({ interval: schedule.interval }), + enabled: true, + updatedBy: username, + updatedAt: now.toISOString(), + executionStatus: { + status: 'pending', + lastDuration: 0, + lastExecutionDate: now.toISOString(), + error: null, + warning: null, + }, + }); + + try { + await context.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); + } catch (e) { + throw e; + } + } + + let scheduledTaskIdToCreate: string | null = null; + if (attributes.scheduledTaskId) { + // If scheduledTaskId defined in rule SO, make sure it exists + try { + await context.taskManager.get(attributes.scheduledTaskId); + } catch (err) { + scheduledTaskIdToCreate = id; + } + } else { + // If scheduledTaskId doesn't exist in rule SO, set it to rule ID + scheduledTaskIdToCreate = id; + } + + if (scheduledTaskIdToCreate) { + // Schedule the task if it doesn't exist + const scheduledTask = await scheduleTask(context, { + id, + consumer: attributes.consumer, + ruleTypeId: attributes.alertTypeId, + schedule: attributes.schedule as IntervalSchedule, + throwOnConflict: false, + }); + await context.unsecuredSavedObjectsClient.update('alert', id, { + scheduledTaskId: scheduledTask.id, + }); + } else { + // Task exists so set enabled to true + await context.taskManager.bulkEnable([attributes.scheduledTaskId!]); + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/find.ts b/x-pack/plugins/alerting/server/rules_client/methods/find.ts new file mode 100644 index 0000000000000..080c72c624cb3 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/find.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 Boom from '@hapi/boom'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { pick } from 'lodash'; +import { KueryNode, nodeBuilder } from '@kbn/es-query'; +import { RawRule, RuleTypeParams, SanitizedRule } from '../../types'; +import { AlertingAuthorizationEntity } from '../../authorization'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { + mapSortField, + validateOperationOnAttributes, + buildKueryNodeFilter, + includeFieldsRequiredForAuthentication, +} from '../common'; +import { + getModifiedField, + getModifiedSearchFields, + getModifiedSearch, + modifyFilterKueryNode, +} from '../common/mapped_params_utils'; +import { alertingAuthorizationFilterOpts } from '../common/constants'; +import { getAlertFromRaw } from '../lib/get_alert_from_raw'; +import type { IndexType, RulesClientContext } from '../types'; + +export interface FindParams { + options?: FindOptions; + excludeFromPublicApi?: boolean; + includeSnoozeData?: boolean; +} + +export interface FindOptions extends IndexType { + perPage?: number; + page?: number; + search?: string; + defaultSearchOperator?: 'AND' | 'OR'; + searchFields?: string[]; + sortField?: string; + sortOrder?: estypes.SortOrder; + hasReference?: { + type: string; + id: string; + }; + fields?: string[]; + filter?: string | KueryNode; +} + +export interface FindResult { + page: number; + perPage: number; + total: number; + data: Array>; +} + +export async function find( + context: RulesClientContext, + { + options: { fields, ...options } = {}, + excludeFromPublicApi = false, + includeSnoozeData = false, + }: FindParams = {} +): Promise> { + let authorizationTuple; + try { + authorizationTuple = await context.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + alertingAuthorizationFilterOpts + ); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.FIND, + error, + }) + ); + throw error; + } + + const { filter: authorizationFilter, ensureRuleTypeIsAuthorized } = authorizationTuple; + + const filterKueryNode = buildKueryNodeFilter(options.filter); + let sortField = mapSortField(options.sortField); + if (excludeFromPublicApi) { + try { + validateOperationOnAttributes( + filterKueryNode, + sortField, + options.searchFields, + context.fieldsToExcludeFromPublicApi + ); + } catch (error) { + throw Boom.badRequest(`Error find rules: ${error.message}`); + } + } + + sortField = mapSortField(getModifiedField(options.sortField)); + + // Generate new modified search and search fields, translating certain params properties + // to mapped_params. Thus, allowing for sort/search/filtering on params. + // We do the modifcation after the validate check to make sure the public API does not + // use the mapped_params in their queries. + options = { + ...options, + ...(options.searchFields && { searchFields: getModifiedSearchFields(options.searchFields) }), + ...(options.search && { search: getModifiedSearch(options.searchFields, options.search) }), + }; + + // Modifies kuery node AST to translate params filter and the filter value to mapped_params. + // This translation is done in place, and therefore is not a pure function. + if (filterKueryNode) { + modifyFilterKueryNode({ astFilter: filterKueryNode }); + } + + const { + page, + per_page: perPage, + total, + saved_objects: data, + } = await context.unsecuredSavedObjectsClient.find({ + ...options, + sortField, + filter: + (authorizationFilter && filterKueryNode + ? nodeBuilder.and([filterKueryNode, authorizationFilter as KueryNode]) + : authorizationFilter) ?? filterKueryNode, + fields: fields ? includeFieldsRequiredForAuthentication(fields) : fields, + type: 'alert', + }); + + const authorizedData = data.map(({ id, attributes, references }) => { + try { + ensureRuleTypeIsAuthorized( + attributes.alertTypeId, + attributes.consumer, + AlertingAuthorizationEntity.Rule + ); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.FIND, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + return getAlertFromRaw( + context, + id, + attributes.alertTypeId, + fields ? (pick(attributes, fields) as RawRule) : attributes, + references, + false, + excludeFromPublicApi, + includeSnoozeData + ); + }); + + authorizedData.forEach(({ id }) => + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.FIND, + savedObject: { type: 'alert', id }, + }) + ) + ); + + return { + page, + perPage, + total, + data: authorizedData, + }; +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/get.ts b/x-pack/plugins/alerting/server/rules_client/methods/get.ts new file mode 100644 index 0000000000000..932772f06d209 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/get.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RawRule, SanitizedRule, RuleTypeParams, SanitizedRuleWithLegacyId } from '../../types'; +import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { getAlertFromRaw } from '../lib/get_alert_from_raw'; +import { RulesClientContext } from '../types'; + +export interface GetParams { + id: string; + includeLegacyId?: boolean; + includeSnoozeData?: boolean; + excludeFromPublicApi?: boolean; +} + +export async function get( + context: RulesClientContext, + { + id, + includeLegacyId = false, + includeSnoozeData = false, + excludeFromPublicApi = false, + }: GetParams +): Promise | SanitizedRuleWithLegacyId> { + const result = await context.unsecuredSavedObjectsClient.get('alert', id); + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: result.attributes.alertTypeId, + consumer: result.attributes.consumer, + operation: ReadOperations.Get, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET, + savedObject: { type: 'alert', id }, + }) + ); + return getAlertFromRaw( + context, + result.id, + result.attributes.alertTypeId, + result.attributes, + result.references, + includeLegacyId, + excludeFromPublicApi, + includeSnoozeData + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/get_action_error_log.ts b/x-pack/plugins/alerting/server/rules_client/methods/get_action_error_log.ts new file mode 100644 index 0000000000000..ebd1862d6b3f6 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/get_action_error_log.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KueryNode } from '@kbn/es-query'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { SanitizedRuleWithLegacyId } from '../../types'; +import { convertEsSortToEventLogSort } from '../../lib'; +import { + ReadOperations, + AlertingAuthorizationEntity, + AlertingAuthorizationFilterType, +} from '../../authorization'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { IExecutionErrorsResult } from '../../../common'; +import { formatExecutionErrorsResult } from '../../lib/format_execution_log_errors'; +import { parseDate } from '../common'; +import { RulesClientContext } from '../types'; +import { get } from './get'; + +const actionErrorLogDefaultFilter = + 'event.provider:actions AND ((event.action:execute AND (event.outcome:failure OR kibana.alerting.status:warning)) OR (event.action:execute-timeout))'; + +export interface GetActionErrorLogByIdParams { + id: string; + dateStart: string; + dateEnd?: string; + filter?: string; + page: number; + perPage: number; + sort: estypes.Sort; + namespace?: string; +} + +export async function getActionErrorLog( + context: RulesClientContext, + { id, dateStart, dateEnd, filter, page, perPage, sort }: GetActionErrorLogByIdParams +): Promise { + context.logger.debug(`getActionErrorLog(): getting action error logs for rule ${id}`); + const rule = (await get(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId; + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: rule.alertTypeId, + consumer: rule.consumer, + operation: ReadOperations.GetActionErrorLog, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_ACTION_ERROR_LOG, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_ACTION_ERROR_LOG, + savedObject: { type: 'alert', id }, + }) + ); + + // default duration of instance summary is 60 * rule interval + const dateNow = new Date(); + const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); + const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); + + const eventLogClient = await context.getEventLogClient(); + + try { + const errorResult = await eventLogClient.findEventsBySavedObjectIds( + 'alert', + [id], + { + start: parsedDateStart.toISOString(), + end: parsedDateEnd.toISOString(), + page, + per_page: perPage, + filter: filter + ? `(${actionErrorLogDefaultFilter}) AND (${filter})` + : actionErrorLogDefaultFilter, + sort: convertEsSortToEventLogSort(sort), + }, + rule.legacyId !== null ? [rule.legacyId] : undefined + ); + return formatExecutionErrorsResult(errorResult); + } catch (err) { + context.logger.debug( + `rulesClient.getActionErrorLog(): error searching event log for rule ${id}: ${err.message}` + ); + throw err; + } +} + +export async function getActionErrorLogWithAuth( + context: RulesClientContext, + { id, dateStart, dateEnd, filter, page, perPage, sort, namespace }: GetActionErrorLogByIdParams +): Promise { + context.logger.debug(`getActionErrorLogWithAuth(): getting action error logs for rule ${id}`); + + let authorizationTuple; + try { + authorizationTuple = await context.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Alert, + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'kibana.alert.rule.rule_type_id', + consumer: 'kibana.alert.rule.consumer', + }, + } + ); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_ACTION_ERROR_LOG, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_ACTION_ERROR_LOG, + savedObject: { type: 'alert', id }, + }) + ); + + // default duration of instance summary is 60 * rule interval + const dateNow = new Date(); + const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); + const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); + + const eventLogClient = await context.getEventLogClient(); + + try { + const errorResult = await eventLogClient.findEventsWithAuthFilter( + 'alert', + [id], + authorizationTuple.filter as KueryNode, + namespace, + { + start: parsedDateStart.toISOString(), + end: parsedDateEnd.toISOString(), + page, + per_page: perPage, + filter: filter + ? `(${actionErrorLogDefaultFilter}) AND (${filter})` + : actionErrorLogDefaultFilter, + sort: convertEsSortToEventLogSort(sort), + } + ); + return formatExecutionErrorsResult(errorResult); + } catch (err) { + context.logger.debug( + `rulesClient.getActionErrorLog(): error searching event log for rule ${id}: ${err.message}` + ); + throw err; + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/get_alert_state.ts b/x-pack/plugins/alerting/server/rules_client/methods/get_alert_state.ts new file mode 100644 index 0000000000000..6497428e1c2f2 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/get_alert_state.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 { RuleTaskState } from '../../types'; +import { taskInstanceToAlertTaskInstance } from '../../task_runner/alert_task_instance'; +import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { RulesClientContext } from '../types'; +import { get } from './get'; + +export interface GetAlertStateParams { + id: string; +} +export async function getAlertState( + context: RulesClientContext, + { id }: GetAlertStateParams +): Promise { + const alert = await get(context, { id }); + await context.authorization.ensureAuthorized({ + ruleTypeId: alert.alertTypeId, + consumer: alert.consumer, + operation: ReadOperations.GetRuleState, + entity: AlertingAuthorizationEntity.Rule, + }); + if (alert.scheduledTaskId) { + const { state } = taskInstanceToAlertTaskInstance( + await context.taskManager.get(alert.scheduledTaskId), + alert + ); + return state; + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/get_alert_summary.ts b/x-pack/plugins/alerting/server/rules_client/methods/get_alert_summary.ts new file mode 100644 index 0000000000000..e841423ad1949 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/get_alert_summary.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IEvent } from '@kbn/event-log-plugin/server'; +import { AlertSummary, SanitizedRuleWithLegacyId } from '../../types'; +import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { alertSummaryFromEventLog } from '../../lib/alert_summary_from_event_log'; +import { parseDuration } from '../../../common/parse_duration'; +import { parseDate } from '../common'; +import { RulesClientContext } from '../types'; +import { get } from './get'; + +export interface GetAlertSummaryParams { + id: string; + dateStart?: string; + numberOfExecutions?: number; +} + +export async function getAlertSummary( + context: RulesClientContext, + { id, dateStart, numberOfExecutions }: GetAlertSummaryParams +): Promise { + context.logger.debug(`getAlertSummary(): getting alert ${id}`); + const rule = (await get(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId; + + await context.authorization.ensureAuthorized({ + ruleTypeId: rule.alertTypeId, + consumer: rule.consumer, + operation: ReadOperations.GetAlertSummary, + entity: AlertingAuthorizationEntity.Rule, + }); + + const dateNow = new Date(); + const durationMillis = parseDuration(rule.schedule.interval) * (numberOfExecutions ?? 60); + const defaultDateStart = new Date(dateNow.valueOf() - durationMillis); + const parsedDateStart = parseDate(dateStart, 'dateStart', defaultDateStart); + + const eventLogClient = await context.getEventLogClient(); + + context.logger.debug(`getAlertSummary(): search the event log for rule ${id}`); + let events: IEvent[]; + let executionEvents: IEvent[]; + + try { + const [queryResults, executionResults] = await Promise.all([ + eventLogClient.findEventsBySavedObjectIds( + 'alert', + [id], + { + page: 1, + per_page: 10000, + start: parsedDateStart.toISOString(), + sort: [{ sort_field: '@timestamp', sort_order: 'desc' }], + end: dateNow.toISOString(), + }, + rule.legacyId !== null ? [rule.legacyId] : undefined + ), + eventLogClient.findEventsBySavedObjectIds( + 'alert', + [id], + { + page: 1, + per_page: numberOfExecutions ?? 60, + filter: 'event.provider: alerting AND event.action:execute', + sort: [{ sort_field: '@timestamp', sort_order: 'desc' }], + end: dateNow.toISOString(), + }, + rule.legacyId !== null ? [rule.legacyId] : undefined + ), + ]); + events = queryResults.data; + executionEvents = executionResults.data; + } catch (err) { + context.logger.debug( + `rulesClient.getAlertSummary(): error searching event log for rule ${id}: ${err.message}` + ); + events = []; + executionEvents = []; + } + + return alertSummaryFromEventLog({ + rule, + events, + executionEvents, + dateStart: parsedDateStart.toISOString(), + dateEnd: dateNow.toISOString(), + }); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/get_execution_kpi.ts b/x-pack/plugins/alerting/server/rules_client/methods/get_execution_kpi.ts new file mode 100644 index 0000000000000..734df53c9cb29 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/get_execution_kpi.ts @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KueryNode } from '@kbn/es-query'; +import { SanitizedRuleWithLegacyId } from '../../types'; +import { + ReadOperations, + AlertingAuthorizationEntity, + AlertingAuthorizationFilterType, +} from '../../authorization'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { + formatExecutionKPIResult, + getExecutionKPIAggregation, +} from '../../lib/get_execution_log_aggregation'; +import { RulesClientContext } from '../types'; +import { parseDate } from '../common'; +import { get } from './get'; + +export interface GetRuleExecutionKPIParams { + id: string; + dateStart: string; + dateEnd?: string; + filter?: string; +} + +export interface GetGlobalExecutionKPIParams { + dateStart: string; + dateEnd?: string; + filter?: string; + namespaces?: Array; +} + +export async function getRuleExecutionKPI( + context: RulesClientContext, + { id, dateStart, dateEnd, filter }: GetRuleExecutionKPIParams +) { + context.logger.debug(`getRuleExecutionKPI(): getting execution KPI for rule ${id}`); + const rule = (await get(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId; + + try { + // Make sure user has access to this rule + await context.authorization.ensureAuthorized({ + ruleTypeId: rule.alertTypeId, + consumer: rule.consumer, + operation: ReadOperations.GetRuleExecutionKPI, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_RULE_EXECUTION_KPI, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_RULE_EXECUTION_KPI, + savedObject: { type: 'alert', id }, + }) + ); + + // default duration of instance summary is 60 * rule interval + const dateNow = new Date(); + const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); + const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); + + const eventLogClient = await context.getEventLogClient(); + + try { + const aggResult = await eventLogClient.aggregateEventsBySavedObjectIds( + 'alert', + [id], + { + start: parsedDateStart.toISOString(), + end: parsedDateEnd.toISOString(), + aggs: getExecutionKPIAggregation(filter), + }, + rule.legacyId !== null ? [rule.legacyId] : undefined + ); + + return formatExecutionKPIResult(aggResult); + } catch (err) { + context.logger.debug( + `rulesClient.getRuleExecutionKPI(): error searching execution KPI for rule ${id}: ${err.message}` + ); + throw err; + } +} + +export async function getGlobalExecutionKpiWithAuth( + context: RulesClientContext, + { dateStart, dateEnd, filter, namespaces }: GetGlobalExecutionKPIParams +) { + context.logger.debug(`getGlobalExecutionLogWithAuth(): getting global execution log`); + + let authorizationTuple; + try { + authorizationTuple = await context.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Alert, + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'kibana.alert.rule.rule_type_id', + consumer: 'kibana.alert.rule.consumer', + }, + } + ); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_GLOBAL_EXECUTION_KPI, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_GLOBAL_EXECUTION_KPI, + }) + ); + + const dateNow = new Date(); + const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); + const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); + + const eventLogClient = await context.getEventLogClient(); + + try { + const aggResult = await eventLogClient.aggregateEventsWithAuthFilter( + 'alert', + authorizationTuple.filter as KueryNode, + { + start: parsedDateStart.toISOString(), + end: parsedDateEnd.toISOString(), + aggs: getExecutionKPIAggregation(filter), + }, + namespaces + ); + + return formatExecutionKPIResult(aggResult); + } catch (err) { + context.logger.debug( + `rulesClient.getGlobalExecutionKpiWithAuth(): error searching global execution KPI: ${err.message}` + ); + throw err; + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/get_execution_log.ts b/x-pack/plugins/alerting/server/rules_client/methods/get_execution_log.ts new file mode 100644 index 0000000000000..006109d71b4b5 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/get_execution_log.ts @@ -0,0 +1,176 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { KueryNode } from '@kbn/es-query'; +import { SanitizedRuleWithLegacyId } from '../../types'; +import { + ReadOperations, + AlertingAuthorizationEntity, + AlertingAuthorizationFilterType, +} from '../../authorization'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { + formatExecutionLogResult, + getExecutionLogAggregation, +} from '../../lib/get_execution_log_aggregation'; +import { IExecutionLogResult } from '../../../common'; +import { parseDate } from '../common'; +import { RulesClientContext } from '../types'; +import { get } from './get'; + +export interface GetExecutionLogByIdParams { + id: string; + dateStart: string; + dateEnd?: string; + filter?: string; + page: number; + perPage: number; + sort: estypes.Sort; +} + +export interface GetGlobalExecutionLogParams { + dateStart: string; + dateEnd?: string; + filter?: string; + page: number; + perPage: number; + sort: estypes.Sort; + namespaces?: Array; +} + +export async function getExecutionLogForRule( + context: RulesClientContext, + { id, dateStart, dateEnd, filter, page, perPage, sort }: GetExecutionLogByIdParams +): Promise { + context.logger.debug(`getExecutionLogForRule(): getting execution log for rule ${id}`); + const rule = (await get(context, { id, includeLegacyId: true })) as SanitizedRuleWithLegacyId; + + try { + // Make sure user has access to this rule + await context.authorization.ensureAuthorized({ + ruleTypeId: rule.alertTypeId, + consumer: rule.consumer, + operation: ReadOperations.GetExecutionLog, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_EXECUTION_LOG, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_EXECUTION_LOG, + savedObject: { type: 'alert', id }, + }) + ); + + // default duration of instance summary is 60 * rule interval + const dateNow = new Date(); + const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); + const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); + + const eventLogClient = await context.getEventLogClient(); + + try { + const aggResult = await eventLogClient.aggregateEventsBySavedObjectIds( + 'alert', + [id], + { + start: parsedDateStart.toISOString(), + end: parsedDateEnd.toISOString(), + aggs: getExecutionLogAggregation({ + filter, + page, + perPage, + sort, + }), + }, + rule.legacyId !== null ? [rule.legacyId] : undefined + ); + + return formatExecutionLogResult(aggResult); + } catch (err) { + context.logger.debug( + `rulesClient.getExecutionLogForRule(): error searching event log for rule ${id}: ${err.message}` + ); + throw err; + } +} + +export async function getGlobalExecutionLogWithAuth( + context: RulesClientContext, + { dateStart, dateEnd, filter, page, perPage, sort, namespaces }: GetGlobalExecutionLogParams +): Promise { + context.logger.debug(`getGlobalExecutionLogWithAuth(): getting global execution log`); + + let authorizationTuple; + try { + authorizationTuple = await context.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Alert, + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'kibana.alert.rule.rule_type_id', + consumer: 'kibana.alert.rule.consumer', + }, + } + ); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_GLOBAL_EXECUTION_LOG, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.GET_GLOBAL_EXECUTION_LOG, + }) + ); + + const dateNow = new Date(); + const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); + const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); + + const eventLogClient = await context.getEventLogClient(); + + try { + const aggResult = await eventLogClient.aggregateEventsWithAuthFilter( + 'alert', + authorizationTuple.filter as KueryNode, + { + start: parsedDateStart.toISOString(), + end: parsedDateEnd.toISOString(), + aggs: getExecutionLogAggregation({ + filter, + page, + perPage, + sort, + }), + }, + namespaces + ); + + return formatExecutionLogResult(aggResult); + } catch (err) { + context.logger.debug( + `rulesClient.getGlobalExecutionLogWithAuth(): error searching global event log: ${err.message}` + ); + throw err; + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/list_alert_types.ts b/x-pack/plugins/alerting/server/rules_client/methods/list_alert_types.ts new file mode 100644 index 0000000000000..eabe15834d6d6 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/list_alert_types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { WriteOperations, ReadOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { RulesClientContext } from '../types'; + +export async function listAlertTypes(context: RulesClientContext) { + return await context.authorization.filterByRuleTypeAuthorization( + context.ruleTypeRegistry.list(), + [ReadOperations.Get, WriteOperations.Create], + AlertingAuthorizationEntity.Rule + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/mute_all.ts b/x-pack/plugins/alerting/server/rules_client/methods/mute_all.ts new file mode 100644 index 0000000000000..4ac6ad207fdc7 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/mute_all.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RawRule } from '../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { partiallyUpdateAlert } from '../../saved_objects'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; +import { updateMeta } from '../lib'; +import { clearUnscheduledSnooze } from '../common'; + +export async function muteAll(context: RulesClientContext, { id }: { id: string }): Promise { + return await retryIfConflicts( + context.logger, + `rulesClient.muteAll('${id}')`, + async () => await muteAllWithOCC(context, { id }) + ); +} + +async function muteAllWithOCC(context: RulesClientContext, { id }: { id: string }) { + const { attributes, version } = await context.unsecuredSavedObjectsClient.get( + 'alert', + id + ); + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.MuteAll, + entity: AlertingAuthorizationEntity.Rule, + }); + + if (attributes.actions.length) { + await context.actionsAuthorization.ensureAuthorized('execute'); + } + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.MUTE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.MUTE, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); + + const updateAttributes = updateMeta(context, { + muteAll: true, + mutedInstanceIds: [], + snoozeSchedule: clearUnscheduledSnooze(attributes), + updatedBy: await context.getUserName(), + updatedAt: new Date().toISOString(), + }); + const updateOptions = { version }; + + await partiallyUpdateAlert( + context.unsecuredSavedObjectsClient, + id, + updateAttributes, + updateOptions + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/mute_instance.ts b/x-pack/plugins/alerting/server/rules_client/methods/mute_instance.ts new file mode 100644 index 0000000000000..67e78b9851945 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/mute_instance.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Rule } from '../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { MuteOptions } from '../types'; +import { RulesClientContext } from '../types'; +import { updateMeta } from '../lib'; + +export async function muteInstance( + context: RulesClientContext, + { alertId, alertInstanceId }: MuteOptions +): Promise { + return await retryIfConflicts( + context.logger, + `rulesClient.muteInstance('${alertId}')`, + async () => await muteInstanceWithOCC(context, { alertId, alertInstanceId }) + ); +} + +async function muteInstanceWithOCC( + context: RulesClientContext, + { alertId, alertInstanceId }: MuteOptions +) { + const { attributes, version } = await context.unsecuredSavedObjectsClient.get( + 'alert', + alertId + ); + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.MuteAlert, + entity: AlertingAuthorizationEntity.Rule, + }); + + if (attributes.actions.length) { + await context.actionsAuthorization.ensureAuthorized('execute'); + } + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.MUTE_ALERT, + savedObject: { type: 'alert', id: alertId }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.MUTE_ALERT, + outcome: 'unknown', + savedObject: { type: 'alert', id: alertId }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); + + const mutedInstanceIds = attributes.mutedInstanceIds || []; + if (!attributes.muteAll && !mutedInstanceIds.includes(alertInstanceId)) { + mutedInstanceIds.push(alertInstanceId); + await context.unsecuredSavedObjectsClient.update( + 'alert', + alertId, + updateMeta(context, { + mutedInstanceIds, + updatedBy: await context.getUserName(), + updatedAt: new Date().toISOString(), + }), + { version } + ); + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/resolve.ts b/x-pack/plugins/alerting/server/rules_client/methods/resolve.ts new file mode 100644 index 0000000000000..539e4c089d36d --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/resolve.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 { RawRule, RuleTypeParams, ResolvedSanitizedRule } from '../../types'; +import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { getAlertFromRaw } from '../lib/get_alert_from_raw'; +import { RulesClientContext } from '../types'; + +export interface ResolveParams { + id: string; + includeLegacyId?: boolean; + includeSnoozeData?: boolean; +} + +export async function resolve( + context: RulesClientContext, + { id, includeLegacyId, includeSnoozeData = false }: ResolveParams +): Promise> { + const { saved_object: result, ...resolveResponse } = + await context.unsecuredSavedObjectsClient.resolve('alert', id); + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: result.attributes.alertTypeId, + consumer: result.attributes.consumer, + operation: ReadOperations.Get, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.RESOLVE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.RESOLVE, + savedObject: { type: 'alert', id }, + }) + ); + + const rule = getAlertFromRaw( + context, + result.id, + result.attributes.alertTypeId, + result.attributes, + result.references, + includeLegacyId, + false, + includeSnoozeData + ); + + return { + ...rule, + ...resolveResponse, + }; +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/run_soon.ts b/x-pack/plugins/alerting/server/rules_client/methods/run_soon.ts new file mode 100644 index 0000000000000..d683b5fbafe4f --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/run_soon.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { ConcreteTaskInstance, TaskStatus } from '@kbn/task-manager-plugin/server'; +import { Rule } from '../../types'; +import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; + +export async function runSoon(context: RulesClientContext, { id }: { id: string }) { + const { attributes } = await context.unsecuredSavedObjectsClient.get('alert', id); + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: ReadOperations.RunSoon, + entity: AlertingAuthorizationEntity.Rule, + }); + + if (attributes.actions.length) { + await context.actionsAuthorization.ensureAuthorized('execute'); + } + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.RUN_SOON, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.RUN_SOON, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); + + // Check that the rule is enabled + if (!attributes.enabled) { + return i18n.translate('xpack.alerting.rulesClient.runSoon.disabledRuleError', { + defaultMessage: 'Error running rule: rule is disabled', + }); + } + + let taskDoc: ConcreteTaskInstance | null = null; + try { + taskDoc = attributes.scheduledTaskId + ? await context.taskManager.get(attributes.scheduledTaskId) + : null; + } catch (err) { + return i18n.translate('xpack.alerting.rulesClient.runSoon.getTaskError', { + defaultMessage: 'Error running rule: {errMessage}', + values: { + errMessage: err.message, + }, + }); + } + + if ( + taskDoc && + (taskDoc.status === TaskStatus.Claiming || taskDoc.status === TaskStatus.Running) + ) { + return i18n.translate('xpack.alerting.rulesClient.runSoon.ruleIsRunning', { + defaultMessage: 'Rule is already running', + }); + } + + try { + await context.taskManager.runSoon(attributes.scheduledTaskId ? attributes.scheduledTaskId : id); + } catch (err) { + return i18n.translate('xpack.alerting.rulesClient.runSoon.runSoonError', { + defaultMessage: 'Error running rule: {errMessage}', + values: { + errMessage: err.message, + }, + }); + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/snooze.ts b/x-pack/plugins/alerting/server/rules_client/methods/snooze.ts new file mode 100644 index 0000000000000..04585bca002b0 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/snooze.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Boom from '@hapi/boom'; +import { RawRule, RuleSnoozeSchedule } from '../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { partiallyUpdateAlert } from '../../saved_objects'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { validateSnoozeStartDate } from '../../lib/validate_snooze_date'; +import { RuleMutedError } from '../../lib/errors/rule_muted'; +import { RulesClientContext } from '../types'; +import { getSnoozeAttributes, verifySnoozeScheduleLimit } from '../common'; +import { updateMeta } from '../lib'; + +export interface SnoozeParams { + id: string; + snoozeSchedule: RuleSnoozeSchedule; +} + +export async function snooze( + context: RulesClientContext, + { id, snoozeSchedule }: SnoozeParams +): Promise { + const snoozeDateValidationMsg = validateSnoozeStartDate(snoozeSchedule.rRule.dtstart); + if (snoozeDateValidationMsg) { + throw new RuleMutedError(snoozeDateValidationMsg); + } + + return await retryIfConflicts( + context.logger, + `rulesClient.snooze('${id}', ${JSON.stringify(snoozeSchedule, null, 4)})`, + async () => await snoozeWithOCC(context, { id, snoozeSchedule }) + ); +} + +async function snoozeWithOCC( + context: RulesClientContext, + { + id, + snoozeSchedule, + }: { + id: string; + snoozeSchedule: RuleSnoozeSchedule; + } +) { + const { attributes, version } = await context.unsecuredSavedObjectsClient.get( + 'alert', + id + ); + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.Snooze, + entity: AlertingAuthorizationEntity.Rule, + }); + + if (attributes.actions.length) { + await context.actionsAuthorization.ensureAuthorized('execute'); + } + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.SNOOZE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.SNOOZE, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); + + const newAttrs = getSnoozeAttributes(attributes, snoozeSchedule); + + try { + verifySnoozeScheduleLimit(newAttrs); + } catch (error) { + throw Boom.badRequest(error.message); + } + + const updateAttributes = updateMeta(context, { + ...newAttrs, + updatedBy: await context.getUserName(), + updatedAt: new Date().toISOString(), + }); + const updateOptions = { version }; + + await partiallyUpdateAlert( + context.unsecuredSavedObjectsClient, + id, + updateAttributes, + updateOptions + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/unmute_all.ts b/x-pack/plugins/alerting/server/rules_client/methods/unmute_all.ts new file mode 100644 index 0000000000000..80819de2b6cc2 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/unmute_all.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 { RawRule } from '../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { partiallyUpdateAlert } from '../../saved_objects'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; +import { updateMeta } from '../lib'; +import { clearUnscheduledSnooze } from '../common'; + +export async function unmuteAll( + context: RulesClientContext, + { id }: { id: string } +): Promise { + return await retryIfConflicts( + context.logger, + `rulesClient.unmuteAll('${id}')`, + async () => await unmuteAllWithOCC(context, { id }) + ); +} + +async function unmuteAllWithOCC(context: RulesClientContext, { id }: { id: string }) { + const { attributes, version } = await context.unsecuredSavedObjectsClient.get( + 'alert', + id + ); + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.UnmuteAll, + entity: AlertingAuthorizationEntity.Rule, + }); + + if (attributes.actions.length) { + await context.actionsAuthorization.ensureAuthorized('execute'); + } + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.UNMUTE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.UNMUTE, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); + + const updateAttributes = updateMeta(context, { + muteAll: false, + mutedInstanceIds: [], + snoozeSchedule: clearUnscheduledSnooze(attributes), + updatedBy: await context.getUserName(), + updatedAt: new Date().toISOString(), + }); + const updateOptions = { version }; + + await partiallyUpdateAlert( + context.unsecuredSavedObjectsClient, + id, + updateAttributes, + updateOptions + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/unmute_instance.ts b/x-pack/plugins/alerting/server/rules_client/methods/unmute_instance.ts new file mode 100644 index 0000000000000..714e5c0a4f8e4 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/unmute_instance.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Rule, RawRule } from '../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { MuteOptions } from '../types'; +import { RulesClientContext } from '../types'; +import { updateMeta } from '../lib'; + +export async function unmuteInstance( + context: RulesClientContext, + { alertId, alertInstanceId }: MuteOptions +): Promise { + return await retryIfConflicts( + context.logger, + `rulesClient.unmuteInstance('${alertId}')`, + async () => await unmuteInstanceWithOCC(context, { alertId, alertInstanceId }) + ); +} + +async function unmuteInstanceWithOCC( + context: RulesClientContext, + { + alertId, + alertInstanceId, + }: { + alertId: string; + alertInstanceId: string; + } +) { + const { attributes, version } = await context.unsecuredSavedObjectsClient.get( + 'alert', + alertId + ); + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.UnmuteAlert, + entity: AlertingAuthorizationEntity.Rule, + }); + if (attributes.actions.length) { + await context.actionsAuthorization.ensureAuthorized('execute'); + } + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.UNMUTE_ALERT, + savedObject: { type: 'alert', id: alertId }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.UNMUTE_ALERT, + outcome: 'unknown', + savedObject: { type: 'alert', id: alertId }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); + + const mutedInstanceIds = attributes.mutedInstanceIds || []; + if (!attributes.muteAll && mutedInstanceIds.includes(alertInstanceId)) { + await context.unsecuredSavedObjectsClient.update( + 'alert', + alertId, + updateMeta(context, { + updatedBy: await context.getUserName(), + updatedAt: new Date().toISOString(), + mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId), + }), + { version } + ); + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/unsnooze.ts b/x-pack/plugins/alerting/server/rules_client/methods/unsnooze.ts new file mode 100644 index 0000000000000..67e8d76e649b4 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/unsnooze.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RawRule } from '../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { partiallyUpdateAlert } from '../../saved_objects'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { RulesClientContext } from '../types'; +import { updateMeta } from '../lib'; +import { getUnsnoozeAttributes } from '../common'; + +export interface UnsnoozeParams { + id: string; + scheduleIds?: string[]; +} + +export async function unsnooze( + context: RulesClientContext, + { id, scheduleIds }: UnsnoozeParams +): Promise { + return await retryIfConflicts( + context.logger, + `rulesClient.unsnooze('${id}')`, + async () => await unsnoozeWithOCC(context, { id, scheduleIds }) + ); +} + +async function unsnoozeWithOCC(context: RulesClientContext, { id, scheduleIds }: UnsnoozeParams) { + const { attributes, version } = await context.unsecuredSavedObjectsClient.get( + 'alert', + id + ); + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.Unsnooze, + entity: AlertingAuthorizationEntity.Rule, + }); + + if (attributes.actions.length) { + await context.actionsAuthorization.ensureAuthorized('execute'); + } + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.UNSNOOZE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.UNSNOOZE, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); + const newAttrs = getUnsnoozeAttributes(attributes, scheduleIds); + + const updateAttributes = updateMeta(context, { + ...newAttrs, + updatedBy: await context.getUserName(), + updatedAt: new Date().toISOString(), + }); + const updateOptions = { version }; + + await partiallyUpdateAlert( + context.unsecuredSavedObjectsClient, + id, + updateAttributes, + updateOptions + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/update.ts b/x-pack/plugins/alerting/server/rules_client/methods/update.ts new file mode 100644 index 0000000000000..289f5fe007874 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/update.ts @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Boom from '@hapi/boom'; +import { isEqual } from 'lodash'; +import { SavedObject } from '@kbn/core/server'; +import { + PartialRule, + RawRule, + RuleTypeParams, + RuleNotifyWhenType, + IntervalSchedule, +} from '../../types'; +import { validateRuleTypeParams, getRuleNotifyWhenType } from '../../lib'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { parseDuration } from '../../../common/parse_duration'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { getMappedParams } from '../common/mapped_params_utils'; +import { NormalizedAlertAction, RulesClientContext } from '../types'; +import { validateActions, extractReferences, updateMeta, getPartialRuleFromRaw } from '../lib'; +import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common'; + +export interface UpdateOptions { + id: string; + data: { + name: string; + tags: string[]; + schedule: IntervalSchedule; + actions: NormalizedAlertAction[]; + params: Params; + throttle?: string | null; + notifyWhen?: RuleNotifyWhenType | null; + }; +} + +export async function update( + context: RulesClientContext, + { id, data }: UpdateOptions +): Promise> { + return await retryIfConflicts( + context.logger, + `rulesClient.update('${id}')`, + async () => await updateWithOCC(context, { id, data }) + ); +} + +async function updateWithOCC( + context: RulesClientContext, + { id, data }: UpdateOptions +): Promise> { + let alertSavedObject: SavedObject; + + try { + alertSavedObject = + await context.encryptedSavedObjectsClient.getDecryptedAsInternalUser('alert', id, { + namespace: context.namespace, + }); + } catch (e) { + // We'll skip invalidating the API key since we failed to load the decrypted saved object + context.logger.error( + `update(): Failed to load API key to invalidate on alert ${id}: ${e.message}` + ); + // Still attempt to load the object using SOC + alertSavedObject = await context.unsecuredSavedObjectsClient.get('alert', id); + } + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: alertSavedObject.attributes.alertTypeId, + consumer: alertSavedObject.attributes.consumer, + operation: WriteOperations.Update, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.UPDATE, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.UPDATE, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(alertSavedObject.attributes.alertTypeId); + + const updateResult = await updateAlert(context, { id, data }, alertSavedObject); + + await Promise.all([ + alertSavedObject.attributes.apiKey + ? bulkMarkApiKeysForInvalidation( + { apiKeys: [alertSavedObject.attributes.apiKey] }, + context.logger, + context.unsecuredSavedObjectsClient + ) + : null, + (async () => { + if ( + updateResult.scheduledTaskId && + updateResult.schedule && + !isEqual(alertSavedObject.attributes.schedule, updateResult.schedule) + ) { + try { + const { tasks } = await context.taskManager.bulkUpdateSchedules( + [updateResult.scheduledTaskId], + updateResult.schedule + ); + + context.logger.debug( + `Rule update has rescheduled the underlying task: ${updateResult.scheduledTaskId} to run at: ${tasks?.[0]?.runAt}` + ); + } catch (err) { + context.logger.error( + `Rule update failed to run its underlying task. TaskManager bulkUpdateSchedules failed with Error: ${err.message}` + ); + } + } + })(), + ]); + + return updateResult; +} + +async function updateAlert( + context: RulesClientContext, + { id, data }: UpdateOptions, + { attributes, version }: SavedObject +): Promise> { + const ruleType = context.ruleTypeRegistry.get(attributes.alertTypeId); + + // Validate + const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate?.params); + await validateActions(context, ruleType, data); + + // Throw error if schedule interval is less than the minimum and we are enforcing it + const intervalInMs = parseDuration(data.schedule.interval); + if ( + intervalInMs < context.minimumScheduleIntervalInMs && + context.minimumScheduleInterval.enforce + ) { + throw Boom.badRequest( + `Error updating rule: the interval is less than the allowed minimum interval of ${context.minimumScheduleInterval.value}` + ); + } + + // Extract saved object references for this rule + const { + references, + params: updatedParams, + actions, + } = await extractReferences(context, ruleType, data.actions, validatedAlertTypeParams); + + const username = await context.getUserName(); + + let createdAPIKey = null; + try { + createdAPIKey = attributes.enabled + ? await context.createAPIKey(generateAPIKeyName(ruleType.id, data.name)) + : null; + } catch (error) { + throw Boom.badRequest(`Error updating rule: could not create API key - ${error.message}`); + } + + const apiKeyAttributes = apiKeyAsAlertAttributes(createdAPIKey, username); + const notifyWhen = getRuleNotifyWhenType(data.notifyWhen ?? null, data.throttle ?? null); + + let updatedObject: SavedObject; + const createAttributes = updateMeta(context, { + ...attributes, + ...data, + ...apiKeyAttributes, + params: updatedParams as RawRule['params'], + actions, + notifyWhen, + updatedBy: username, + updatedAt: new Date().toISOString(), + }); + + const mappedParams = getMappedParams(updatedParams); + + if (Object.keys(mappedParams).length) { + createAttributes.mapped_params = mappedParams; + } + + try { + updatedObject = await context.unsecuredSavedObjectsClient.create( + 'alert', + createAttributes, + { + id, + overwrite: true, + version, + references, + } + ); + } catch (e) { + // Avoid unused API key + await bulkMarkApiKeysForInvalidation( + { apiKeys: createAttributes.apiKey ? [createAttributes.apiKey] : [] }, + context.logger, + context.unsecuredSavedObjectsClient + ); + + throw e; + } + + // Log warning if schedule interval is less than the minimum but we're not enforcing it + if ( + intervalInMs < context.minimumScheduleIntervalInMs && + !context.minimumScheduleInterval.enforce + ) { + context.logger.warn( + `Rule schedule interval (${data.schedule.interval}) for "${ruleType.id}" rule type with ID "${id}" is less than the minimum value (${context.minimumScheduleInterval.value}). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent such changes.` + ); + } + + return getPartialRuleFromRaw( + context, + id, + ruleType, + updatedObject.attributes, + updatedObject.references, + false, + true + ); +} diff --git a/x-pack/plugins/alerting/server/rules_client/methods/update_api_key.ts b/x-pack/plugins/alerting/server/rules_client/methods/update_api_key.ts new file mode 100644 index 0000000000000..abb7d32404d57 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/methods/update_api_key.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 Boom from '@hapi/boom'; +import { RawRule } from '../../types'; +import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; +import { retryIfConflicts } from '../../lib/retry_if_conflicts'; +import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; +import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; +import { generateAPIKeyName, apiKeyAsAlertAttributes } from '../common'; +import { updateMeta } from '../lib'; +import { RulesClientContext } from '../types'; + +export async function updateApiKey( + context: RulesClientContext, + { id }: { id: string } +): Promise { + return await retryIfConflicts( + context.logger, + `rulesClient.updateApiKey('${id}')`, + async () => await updateApiKeyWithOCC(context, { id }) + ); +} + +async function updateApiKeyWithOCC(context: RulesClientContext, { id }: { id: string }) { + let apiKeyToInvalidate: string | null = null; + let attributes: RawRule; + let version: string | undefined; + + try { + const decryptedAlert = + await context.encryptedSavedObjectsClient.getDecryptedAsInternalUser('alert', id, { + namespace: context.namespace, + }); + apiKeyToInvalidate = decryptedAlert.attributes.apiKey; + attributes = decryptedAlert.attributes; + version = decryptedAlert.version; + } catch (e) { + // We'll skip invalidating the API key since we failed to load the decrypted saved object + context.logger.error( + `updateApiKey(): Failed to load API key to invalidate on alert ${id}: ${e.message}` + ); + // Still attempt to load the attributes and version using SOC + const alert = await context.unsecuredSavedObjectsClient.get('alert', id); + attributes = alert.attributes; + version = alert.version; + } + + try { + await context.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.UpdateApiKey, + entity: AlertingAuthorizationEntity.Rule, + }); + if (attributes.actions.length) { + await context.actionsAuthorization.ensureAuthorized('execute'); + } + } catch (error) { + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.UPDATE_API_KEY, + savedObject: { type: 'alert', id }, + error, + }) + ); + throw error; + } + + const username = await context.getUserName(); + + let createdAPIKey = null; + try { + createdAPIKey = await context.createAPIKey( + generateAPIKeyName(attributes.alertTypeId, attributes.name) + ); + } catch (error) { + throw Boom.badRequest( + `Error updating API key for rule: could not create API key - ${error.message}` + ); + } + + const updateAttributes = updateMeta(context, { + ...attributes, + ...apiKeyAsAlertAttributes(createdAPIKey, username), + updatedAt: new Date().toISOString(), + updatedBy: username, + }); + + context.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.UPDATE_API_KEY, + outcome: 'unknown', + savedObject: { type: 'alert', id }, + }) + ); + + context.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); + + try { + await context.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); + } catch (e) { + // Avoid unused API key + await bulkMarkApiKeysForInvalidation( + { apiKeys: updateAttributes.apiKey ? [updateAttributes.apiKey] : [] }, + context.logger, + context.unsecuredSavedObjectsClient + ); + throw e; + } + + if (apiKeyToInvalidate) { + await bulkMarkApiKeysForInvalidation( + { apiKeys: [apiKeyToInvalidate] }, + context.logger, + context.unsecuredSavedObjectsClient + ); + } +} diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 5feb6ab8c0445..d790ec3587d77 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -5,4512 +5,134 @@ * 2.0. */ -import Semver from 'semver'; -import pMap from 'p-map'; -import Boom from '@hapi/boom'; -import { - omit, - isEqual, - map, - uniq, - pick, - truncate, - trim, - mapValues, - cloneDeep, - isEmpty, -} from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { AlertConsumers } from '@kbn/rule-data-utils'; -import { KueryNode, nodeBuilder } from '@kbn/es-query'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { - Logger, - SavedObjectsClientContract, - SavedObjectReference, - SavedObject, - PluginInitializerContext, - SavedObjectsUtils, - SavedObjectAttributes, - SavedObjectsBulkUpdateObject, - SavedObjectsBulkDeleteObject, - SavedObjectsUpdateResponse, -} from '@kbn/core/server'; -import { ActionsClient, ActionsAuthorization } from '@kbn/actions-plugin/server'; -import { - GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult, - InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, -} from '@kbn/security-plugin/server'; -import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; -import { - ConcreteTaskInstance, - TaskManagerStartContract, - TaskStatus, -} from '@kbn/task-manager-plugin/server'; -import { - IEvent, - IEventLogClient, - IEventLogger, - SAVED_OBJECT_REL_PRIMARY, -} from '@kbn/event-log-plugin/server'; -import { AuditLogger } from '@kbn/security-plugin/server'; -import { - Rule, - PartialRule, - RawRule, - RuleTypeRegistry, - RuleAction, - IntervalSchedule, - SanitizedRule, - RuleTaskState, - AlertSummary, - RuleExecutionStatusValues, - RuleLastRunOutcomeValues, - RuleNotifyWhenType, - RuleTypeParams, - ResolvedSanitizedRule, - RuleWithLegacyId, - SanitizedRuleWithLegacyId, - PartialRuleWithLegacyId, - RuleSnooze, - RuleSnoozeSchedule, - RawAlertInstance as RawAlert, -} from '../types'; -import { - validateRuleTypeParams, - ruleExecutionStatusFromRaw, - getRuleNotifyWhenType, - validateMutatedRuleTypeParams, - convertRuleIdsToKueryNode, - getRuleSnoozeEndTime, - convertEsSortToEventLogSort, - getDefaultMonitoring, - updateMonitoring, - convertMonitoringFromRawAndVerify, - getNextRun, -} from '../lib'; -import { taskInstanceToAlertTaskInstance } from '../task_runner/alert_task_instance'; -import { RegistryRuleType, UntypedNormalizedRuleType } from '../rule_type_registry'; -import { - AlertingAuthorization, - WriteOperations, - ReadOperations, - AlertingAuthorizationEntity, - AlertingAuthorizationFilterType, - AlertingAuthorizationFilterOpts, -} from '../authorization'; -import { parseIsoOrRelativeDate } from '../lib/iso_or_relative_date'; -import { alertSummaryFromEventLog } from '../lib/alert_summary_from_event_log'; +import { SanitizedRule, RuleTypeParams } from '../types'; import { parseDuration } from '../../common/parse_duration'; -import { retryIfConflicts } from '../lib/retry_if_conflicts'; -import { partiallyUpdateAlert } from '../saved_objects'; -import { bulkMarkApiKeysForInvalidation } from '../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; -import { ruleAuditEvent, RuleAuditAction } from './audit_events'; +import { RulesClientContext, BulkOptions, MuteOptions } from './types'; + +import { clone, CloneArguments } from './methods/clone'; +import { create, CreateOptions } from './methods/create'; +import { get, GetParams } from './methods/get'; +import { resolve, ResolveParams } from './methods/resolve'; +import { getAlertState, GetAlertStateParams } from './methods/get_alert_state'; +import { getAlertSummary, GetAlertSummaryParams } from './methods/get_alert_summary'; import { - mapSortField, - validateOperationOnAttributes, - retryIfBulkEditConflicts, - retryIfBulkDeleteConflicts, - retryIfBulkDisableConflicts, - retryIfBulkOperationConflicts, - applyBulkEditOperation, - buildKueryNodeFilter, -} from './lib'; -import { getRuleExecutionStatusPending } from '../lib/rule_execution_status'; -import { Alert } from '../alert'; -import { EVENT_LOG_ACTIONS } from '../plugin'; -import { createAlertEventLogRecordObject } from '../lib/create_alert_event_log_record_object'; + GetExecutionLogByIdParams, + getExecutionLogForRule, + GetGlobalExecutionLogParams, + getGlobalExecutionLogWithAuth, +} from './methods/get_execution_log'; import { - getMappedParams, - getModifiedField, - getModifiedSearchFields, - getModifiedSearch, - modifyFilterKueryNode, -} from './lib/mapped_params_utils'; -import { AlertingRulesConfig } from '../config'; + getActionErrorLog, + GetActionErrorLogByIdParams, + getActionErrorLogWithAuth, +} from './methods/get_action_error_log'; import { - formatExecutionLogResult, - formatExecutionKPIResult, - getExecutionLogAggregation, - getExecutionKPIAggregation, -} from '../lib/get_execution_log_aggregation'; -import { IExecutionLogResult, IExecutionErrorsResult } from '../../common'; -import { validateSnoozeStartDate } from '../lib/validate_snooze_date'; -import { RuleMutedError } from '../lib/errors/rule_muted'; -import { formatExecutionErrorsResult } from '../lib/format_execution_log_errors'; -import { getActiveScheduledSnoozes } from '../lib/is_rule_snoozed'; -import { isSnoozeExpired } from '../lib'; -import { isDetectionEngineAADRuleType } from '../saved_objects/migrations/utils'; - -export interface RegistryAlertTypeWithAuth extends RegistryRuleType { - authorizedConsumers: string[]; -} -type NormalizedAlertAction = Omit; -export type CreateAPIKeyResult = - | { apiKeysEnabled: false } - | { apiKeysEnabled: true; result: SecurityPluginGrantAPIKeyResult }; -export type InvalidateAPIKeyResult = - | { apiKeysEnabled: false } - | { apiKeysEnabled: true; result: SecurityPluginInvalidateAPIKeyResult }; - -export interface RuleAggregation { - status: { - buckets: Array<{ - key: string; - doc_count: number; - }>; - }; - outcome: { - buckets: Array<{ - key: string; - doc_count: number; - }>; - }; - muted: { - buckets: Array<{ - key: number; - key_as_string: string; - doc_count: number; - }>; - }; - enabled: { - buckets: Array<{ - key: number; - key_as_string: string; - doc_count: number; - }>; - }; - snoozed: { - count: { - doc_count: number; - }; - }; - tags: { - buckets: Array<{ - key: string; - doc_count: number; - }>; - }; -} - -export interface RuleBulkOperationAggregation { - alertTypeId: { - buckets: Array<{ - key: string[]; - doc_count: number; - }>; - }; -} - -export interface ConstructorOptions { - logger: Logger; - taskManager: TaskManagerStartContract; - unsecuredSavedObjectsClient: SavedObjectsClientContract; - authorization: AlertingAuthorization; - actionsAuthorization: ActionsAuthorization; - ruleTypeRegistry: RuleTypeRegistry; - minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval']; - encryptedSavedObjectsClient: EncryptedSavedObjectsClient; - spaceId: string; - namespace?: string; - getUserName: () => Promise; - createAPIKey: (name: string) => Promise; - getActionsClient: () => Promise; - getEventLogClient: () => Promise; - kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; - auditLogger?: AuditLogger; - eventLogger?: IEventLogger; -} - -export interface MuteOptions extends IndexType { - alertId: string; - alertInstanceId: string; -} - -export interface SnoozeOptions extends IndexType { - snoozeSchedule: RuleSnoozeSchedule; -} - -export interface FindOptions extends IndexType { - perPage?: number; - page?: number; - search?: string; - defaultSearchOperator?: 'AND' | 'OR'; - searchFields?: string[]; - sortField?: string; - sortOrder?: estypes.SortOrder; - hasReference?: { - type: string; - id: string; - }; - fields?: string[]; - filter?: string | KueryNode; -} - -export type BulkEditFields = keyof Pick< - Rule, - 'actions' | 'tags' | 'schedule' | 'throttle' | 'notifyWhen' | 'snoozeSchedule' | 'apiKey' + GetGlobalExecutionKPIParams, + getGlobalExecutionKpiWithAuth, + getRuleExecutionKPI, + GetRuleExecutionKPIParams, +} from './methods/get_execution_kpi'; +import { find, FindParams } from './methods/find'; +import { aggregate, AggregateOptions } from './methods/aggregate'; +import { deleteRule } from './methods/delete'; +import { update, UpdateOptions } from './methods/update'; +import { bulkDeleteRules } from './methods/bulk_delete'; +import { bulkEdit, BulkEditOptions } from './methods/bulk_edit'; +import { bulkEnableRules } from './methods/bulk_enable'; +import { bulkDisableRules } from './methods/bulk_disable'; +import { updateApiKey } from './methods/update_api_key'; +import { enable } from './methods/enable'; +import { disable } from './methods/disable'; +import { snooze, SnoozeParams } from './methods/snooze'; +import { unsnooze, UnsnoozeParams } from './methods/unsnooze'; +import { clearExpiredSnoozes } from './methods/clear_expired_snoozes'; +import { muteAll } from './methods/mute_all'; +import { unmuteAll } from './methods/unmute_all'; +import { muteInstance } from './methods/mute_instance'; +import { unmuteInstance } from './methods/unmute_instance'; +import { runSoon } from './methods/run_soon'; +import { listAlertTypes } from './methods/list_alert_types'; + +export type ConstructorOptions = Omit< + RulesClientContext, + 'fieldsToExcludeFromPublicApi' | 'minimumScheduleIntervalInMs' >; -export type BulkEditOperation = - | { - operation: 'add' | 'delete' | 'set'; - field: Extract; - value: string[]; - } - | { - operation: 'add' | 'set'; - field: Extract; - value: NormalizedAlertAction[]; - } - | { - operation: 'set'; - field: Extract; - value: Rule['schedule']; - } - | { - operation: 'set'; - field: Extract; - value: Rule['throttle']; - } - | { - operation: 'set'; - field: Extract; - value: Rule['notifyWhen']; - } - | { - operation: 'set'; - field: Extract; - value: RuleSnoozeSchedule; - } - | { - operation: 'delete'; - field: Extract; - value?: string[]; - } - | { - operation: 'set'; - field: Extract; - value?: undefined; - }; - -type RuleParamsModifier = (params: Params) => Promise; - -export interface BulkEditOptionsFilter { - filter?: string | KueryNode; - operations: BulkEditOperation[]; - paramsModifier?: RuleParamsModifier; -} - -export interface BulkEditOptionsIds { - ids: string[]; - operations: BulkEditOperation[]; - paramsModifier?: RuleParamsModifier; -} - -export type BulkEditOptions = - | BulkEditOptionsFilter - | BulkEditOptionsIds; - -interface BulkOptionsFilter { - filter?: string | KueryNode; -} - -interface BulkOptionsIds { - ids?: string[]; -} - -export type BulkOptions = BulkOptionsFilter | BulkOptionsIds; - -export interface BulkOperationError { - message: string; - status?: number; - rule: { - id: string; - name: string; - }; -} - -export interface AggregateOptions extends IndexType { - search?: string; - defaultSearchOperator?: 'AND' | 'OR'; - searchFields?: string[]; - hasReference?: { - type: string; - id: string; - }; - filter?: string | KueryNode; -} - -interface IndexType { - [key: string]: unknown; -} - -export interface AggregateResult { - alertExecutionStatus: { [status: string]: number }; - ruleLastRunOutcome: { [status: string]: number }; - ruleEnabledStatus?: { enabled: number; disabled: number }; - ruleMutedStatus?: { muted: number; unmuted: number }; - ruleSnoozedStatus?: { snoozed: number }; - ruleTags?: string[]; -} - -export interface FindResult { - page: number; - perPage: number; - total: number; - data: Array>; -} - -interface SavedObjectOptions { - id?: string; - migrationVersion?: Record; -} - -export interface CreateOptions { - data: Omit< - Rule, - | 'id' - | 'createdBy' - | 'updatedBy' - | 'createdAt' - | 'updatedAt' - | 'apiKey' - | 'apiKeyOwner' - | 'muteAll' - | 'mutedInstanceIds' - | 'actions' - | 'executionStatus' - | 'snoozeSchedule' - | 'isSnoozedUntil' - | 'lastRun' - | 'nextRun' - > & { actions: NormalizedAlertAction[] }; - options?: SavedObjectOptions; -} - -export interface UpdateOptions { - id: string; - data: { - name: string; - tags: string[]; - schedule: IntervalSchedule; - actions: NormalizedAlertAction[]; - params: Params; - throttle?: string | null; - notifyWhen?: RuleNotifyWhenType | null; - }; -} - -export interface GetAlertSummaryParams { - id: string; - dateStart?: string; - numberOfExecutions?: number; -} - -export interface GetExecutionLogByIdParams { - id: string; - dateStart: string; - dateEnd?: string; - filter?: string; - page: number; - perPage: number; - sort: estypes.Sort; -} - -export interface GetRuleExecutionKPIParams { - id: string; - dateStart: string; - dateEnd?: string; - filter?: string; -} - -export interface GetGlobalExecutionKPIParams { - dateStart: string; - dateEnd?: string; - filter?: string; - namespaces?: Array; -} - -export interface GetGlobalExecutionLogParams { - dateStart: string; - dateEnd?: string; - filter?: string; - page: number; - perPage: number; - sort: estypes.Sort; - namespaces?: Array; -} - -export interface GetActionErrorLogByIdParams { - id: string; - dateStart: string; - dateEnd?: string; - filter?: string; - page: number; - perPage: number; - sort: estypes.Sort; - namespace?: string; -} - -interface ScheduleTaskOptions { - id: string; - consumer: string; - ruleTypeId: string; - schedule: IntervalSchedule; - throwOnConflict: boolean; // whether to throw conflict errors or swallow them -} - -type BulkAction = 'DELETE' | 'ENABLE' | 'DISABLE'; - -// NOTE: Changing this prefix will require a migration to update the prefix in all existing `rule` saved objects -const extractedSavedObjectParamReferenceNamePrefix = 'param:'; +const fieldsToExcludeFromPublicApi: Array = [ + 'monitoring', + 'mapped_params', + 'snoozeSchedule', + 'activeSnoozes', +]; -// NOTE: Changing this prefix will require a migration to update the prefix in all existing `rule` saved objects -const preconfiguredConnectorActionRefPrefix = 'preconfigured:'; - -const MAX_RULES_NUMBER_FOR_BULK_OPERATION = 10000; -const API_KEY_GENERATE_CONCURRENCY = 50; -const RULE_TYPE_CHECKS_CONCURRENCY = 50; - -const actionErrorLogDefaultFilter = - 'event.provider:actions AND ((event.action:execute AND (event.outcome:failure OR kibana.alerting.status:warning)) OR (event.action:execute-timeout))'; - -const alertingAuthorizationFilterOpts: AlertingAuthorizationFilterOpts = { - type: AlertingAuthorizationFilterType.KQL, - fieldNames: { ruleTypeId: 'alert.attributes.alertTypeId', consumer: 'alert.attributes.consumer' }, -}; export class RulesClient { - private readonly logger: Logger; - private readonly getUserName: () => Promise; - private readonly spaceId: string; - private readonly namespace?: string; - private readonly taskManager: TaskManagerStartContract; - private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract; - private readonly authorization: AlertingAuthorization; - private readonly ruleTypeRegistry: RuleTypeRegistry; - private readonly minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval']; - private readonly minimumScheduleIntervalInMs: number; - private readonly createAPIKey: (name: string) => Promise; - private readonly getActionsClient: () => Promise; - private readonly actionsAuthorization: ActionsAuthorization; - private readonly getEventLogClient: () => Promise; - private readonly encryptedSavedObjectsClient: EncryptedSavedObjectsClient; - private readonly kibanaVersion!: PluginInitializerContext['env']['packageInfo']['version']; - private readonly auditLogger?: AuditLogger; - private readonly eventLogger?: IEventLogger; - private readonly fieldsToExcludeFromPublicApi: Array = [ - 'monitoring', - 'mapped_params', - 'snoozeSchedule', - 'activeSnoozes', - ]; - - constructor({ - ruleTypeRegistry, - minimumScheduleInterval, - unsecuredSavedObjectsClient, - authorization, - taskManager, - logger, - spaceId, - namespace, - getUserName, - createAPIKey, - encryptedSavedObjectsClient, - getActionsClient, - actionsAuthorization, - getEventLogClient, - kibanaVersion, - auditLogger, - eventLogger, - }: ConstructorOptions) { - this.logger = logger; - this.getUserName = getUserName; - this.spaceId = spaceId; - this.namespace = namespace; - this.taskManager = taskManager; - this.ruleTypeRegistry = ruleTypeRegistry; - this.minimumScheduleInterval = minimumScheduleInterval; - this.minimumScheduleIntervalInMs = parseDuration(minimumScheduleInterval.value); - this.unsecuredSavedObjectsClient = unsecuredSavedObjectsClient; - this.authorization = authorization; - this.createAPIKey = createAPIKey; - this.encryptedSavedObjectsClient = encryptedSavedObjectsClient; - this.getActionsClient = getActionsClient; - this.actionsAuthorization = actionsAuthorization; - this.getEventLogClient = getEventLogClient; - this.kibanaVersion = kibanaVersion; - this.auditLogger = auditLogger; - this.eventLogger = eventLogger; - } - - public async clone( - id: string, - { newId }: { newId?: string } - ): Promise> { - let ruleSavedObject: SavedObject; - - try { - ruleSavedObject = await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser( - 'alert', - id, - { - namespace: this.namespace, - } - ); - } catch (e) { - // We'll skip invalidating the API key since we failed to load the decrypted saved object - this.logger.error( - `update(): Failed to load API key to invalidate on alert ${id}: ${e.message}` - ); - // Still attempt to load the object using SOC - ruleSavedObject = await this.unsecuredSavedObjectsClient.get('alert', id); - } - - /* - * As the time of the creation of this PR, security solution already have a clone/duplicate API - * with some specific business logic so to avoid weird bugs, I prefer to exclude them from this - * functionality until we resolve our difference - */ - if ( - isDetectionEngineAADRuleType(ruleSavedObject) || - ruleSavedObject.attributes.consumer === AlertConsumers.SIEM - ) { - throw Boom.badRequest( - 'The clone functionality is not enable for rule who belongs to security solution' - ); - } - const ruleName = - ruleSavedObject.attributes.name.indexOf('[Clone]') > 0 - ? ruleSavedObject.attributes.name - : `${ruleSavedObject.attributes.name} [Clone]`; - const ruleId = newId ?? SavedObjectsUtils.generateId(); - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: ruleSavedObject.attributes.alertTypeId, - consumer: ruleSavedObject.attributes.consumer, - operation: WriteOperations.Create, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.CREATE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.ruleTypeRegistry.ensureRuleTypeEnabled(ruleSavedObject.attributes.alertTypeId); - // Throws an error if alert type isn't registered - const ruleType = this.ruleTypeRegistry.get(ruleSavedObject.attributes.alertTypeId); - const username = await this.getUserName(); - const createTime = Date.now(); - const lastRunTimestamp = new Date(); - const legacyId = Semver.lt(this.kibanaVersion, '8.0.0') ? id : null; - let createdAPIKey = null; - try { - createdAPIKey = ruleSavedObject.attributes.enabled - ? await this.createAPIKey(this.generateAPIKeyName(ruleType.id, ruleName)) - : null; - } catch (error) { - throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`); - } - const rawRule: RawRule = { - ...ruleSavedObject.attributes, - name: ruleName, - ...this.apiKeyAsAlertAttributes(createdAPIKey, username), - legacyId, - createdBy: username, - updatedBy: username, - createdAt: new Date(createTime).toISOString(), - updatedAt: new Date(createTime).toISOString(), - snoozeSchedule: [], - muteAll: false, - mutedInstanceIds: [], - executionStatus: getRuleExecutionStatusPending(lastRunTimestamp.toISOString()), - monitoring: getDefaultMonitoring(lastRunTimestamp.toISOString()), - scheduledTaskId: null, - }; - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.CREATE, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - - return await this.createRuleSavedObject({ - intervalInMs: parseDuration(rawRule.schedule.interval), - rawRule, - references: ruleSavedObject.references, - ruleId, - }); - } - - public async create({ - data, - options, - }: CreateOptions): Promise> { - const id = options?.id || SavedObjectsUtils.generateId(); - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: data.alertTypeId, - consumer: data.consumer, - operation: WriteOperations.Create, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.CREATE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.ruleTypeRegistry.ensureRuleTypeEnabled(data.alertTypeId); - - // Throws an error if alert type isn't registered - const ruleType = this.ruleTypeRegistry.get(data.alertTypeId); - - const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate?.params); - const username = await this.getUserName(); - - let createdAPIKey = null; - try { - createdAPIKey = data.enabled - ? await this.createAPIKey(this.generateAPIKeyName(ruleType.id, data.name)) - : null; - } catch (error) { - throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`); - } - - await this.validateActions(ruleType, data); - - // Throw error if schedule interval is less than the minimum and we are enforcing it - const intervalInMs = parseDuration(data.schedule.interval); - if (intervalInMs < this.minimumScheduleIntervalInMs && this.minimumScheduleInterval.enforce) { - throw Boom.badRequest( - `Error creating rule: the interval is less than the allowed minimum interval of ${this.minimumScheduleInterval.value}` - ); - } - - // Extract saved object references for this rule - const { - references, - params: updatedParams, - actions, - } = await this.extractReferences(ruleType, data.actions, validatedAlertTypeParams); - - const createTime = Date.now(); - const lastRunTimestamp = new Date(); - const legacyId = Semver.lt(this.kibanaVersion, '8.0.0') ? id : null; - const notifyWhen = getRuleNotifyWhenType(data.notifyWhen ?? null, data.throttle ?? null); - const throttle = data.throttle ?? null; - - const rawRule: RawRule = { - ...data, - ...this.apiKeyAsAlertAttributes(createdAPIKey, username), - legacyId, - actions, - createdBy: username, - updatedBy: username, - createdAt: new Date(createTime).toISOString(), - updatedAt: new Date(createTime).toISOString(), - snoozeSchedule: [], - params: updatedParams as RawRule['params'], - muteAll: false, - mutedInstanceIds: [], - notifyWhen, - throttle, - executionStatus: getRuleExecutionStatusPending(lastRunTimestamp.toISOString()), - monitoring: getDefaultMonitoring(lastRunTimestamp.toISOString()), - }; - - const mappedParams = getMappedParams(updatedParams); - - if (Object.keys(mappedParams).length) { - rawRule.mapped_params = mappedParams; - } - - return await this.createRuleSavedObject({ - intervalInMs, - rawRule, - references, - ruleId: id, - options, - }); - } - - private async createRuleSavedObject({ - intervalInMs, - rawRule, - references, - ruleId, - options, - }: { - intervalInMs: number; - rawRule: RawRule; - references: SavedObjectReference[]; - ruleId: string; - options?: SavedObjectOptions; - }) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.CREATE, - outcome: 'unknown', - savedObject: { type: 'alert', id: ruleId }, - }) - ); - - let createdAlert: SavedObject; - try { - createdAlert = await this.unsecuredSavedObjectsClient.create( - 'alert', - this.updateMeta(rawRule), - { - ...options, - references, - id: ruleId, - } - ); - } catch (e) { - // Avoid unused API key - await bulkMarkApiKeysForInvalidation( - { apiKeys: rawRule.apiKey ? [rawRule.apiKey] : [] }, - this.logger, - this.unsecuredSavedObjectsClient - ); - - throw e; - } - if (rawRule.enabled) { - let scheduledTask; - try { - scheduledTask = await this.scheduleTask({ - id: createdAlert.id, - consumer: rawRule.consumer, - ruleTypeId: rawRule.alertTypeId, - schedule: rawRule.schedule, - throwOnConflict: true, - }); - } catch (e) { - // Cleanup data, something went wrong scheduling the task - try { - await this.unsecuredSavedObjectsClient.delete('alert', createdAlert.id); - } catch (err) { - // Skip the cleanup error and throw the task manager error to avoid confusion - this.logger.error( - `Failed to cleanup alert "${createdAlert.id}" after scheduling task failed. Error: ${err.message}` - ); - } - throw e; - } - await this.unsecuredSavedObjectsClient.update('alert', createdAlert.id, { - scheduledTaskId: scheduledTask.id, - }); - createdAlert.attributes.scheduledTaskId = scheduledTask.id; - } - - // Log warning if schedule interval is less than the minimum but we're not enforcing it - if (intervalInMs < this.minimumScheduleIntervalInMs && !this.minimumScheduleInterval.enforce) { - this.logger.warn( - `Rule schedule interval (${rawRule.schedule.interval}) for "${createdAlert.attributes.alertTypeId}" rule type with ID "${createdAlert.id}" is less than the minimum value (${this.minimumScheduleInterval.value}). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent creation of these rules.` - ); - } - - return this.getAlertFromRaw( - createdAlert.id, - createdAlert.attributes.alertTypeId, - createdAlert.attributes, - references, - false, - true - ); - } - - public async get({ - id, - includeLegacyId = false, - includeSnoozeData = false, - excludeFromPublicApi = false, - }: { - id: string; - includeLegacyId?: boolean; - includeSnoozeData?: boolean; - excludeFromPublicApi?: boolean; - }): Promise | SanitizedRuleWithLegacyId> { - const result = await this.unsecuredSavedObjectsClient.get('alert', id); - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: result.attributes.alertTypeId, - consumer: result.attributes.consumer, - operation: ReadOperations.Get, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET, - savedObject: { type: 'alert', id }, - }) - ); - return this.getAlertFromRaw( - result.id, - result.attributes.alertTypeId, - result.attributes, - result.references, - includeLegacyId, - excludeFromPublicApi, - includeSnoozeData - ); - } - - public async resolve({ - id, - includeLegacyId, - includeSnoozeData = false, - }: { - id: string; - includeLegacyId?: boolean; - includeSnoozeData?: boolean; - }): Promise> { - const { saved_object: result, ...resolveResponse } = - await this.unsecuredSavedObjectsClient.resolve('alert', id); - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: result.attributes.alertTypeId, - consumer: result.attributes.consumer, - operation: ReadOperations.Get, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.RESOLVE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.RESOLVE, - savedObject: { type: 'alert', id }, - }) - ); - - const rule = this.getAlertFromRaw( - result.id, - result.attributes.alertTypeId, - result.attributes, - result.references, - includeLegacyId, - false, - includeSnoozeData - ); - - return { - ...rule, - ...resolveResponse, - }; - } - - public async getAlertState({ id }: { id: string }): Promise { - const alert = await this.get({ id }); - await this.authorization.ensureAuthorized({ - ruleTypeId: alert.alertTypeId, - consumer: alert.consumer, - operation: ReadOperations.GetRuleState, - entity: AlertingAuthorizationEntity.Rule, - }); - if (alert.scheduledTaskId) { - const { state } = taskInstanceToAlertTaskInstance( - await this.taskManager.get(alert.scheduledTaskId), - alert - ); - return state; - } - } - - public async getAlertSummary({ - id, - dateStart, - numberOfExecutions, - }: GetAlertSummaryParams): Promise { - this.logger.debug(`getAlertSummary(): getting alert ${id}`); - const rule = (await this.get({ id, includeLegacyId: true })) as SanitizedRuleWithLegacyId; - - await this.authorization.ensureAuthorized({ - ruleTypeId: rule.alertTypeId, - consumer: rule.consumer, - operation: ReadOperations.GetAlertSummary, - entity: AlertingAuthorizationEntity.Rule, - }); - - const dateNow = new Date(); - const durationMillis = parseDuration(rule.schedule.interval) * (numberOfExecutions ?? 60); - const defaultDateStart = new Date(dateNow.valueOf() - durationMillis); - const parsedDateStart = parseDate(dateStart, 'dateStart', defaultDateStart); - - const eventLogClient = await this.getEventLogClient(); - - this.logger.debug(`getAlertSummary(): search the event log for rule ${id}`); - let events: IEvent[]; - let executionEvents: IEvent[]; - - try { - const [queryResults, executionResults] = await Promise.all([ - eventLogClient.findEventsBySavedObjectIds( - 'alert', - [id], - { - page: 1, - per_page: 10000, - start: parsedDateStart.toISOString(), - sort: [{ sort_field: '@timestamp', sort_order: 'desc' }], - end: dateNow.toISOString(), - }, - rule.legacyId !== null ? [rule.legacyId] : undefined - ), - eventLogClient.findEventsBySavedObjectIds( - 'alert', - [id], - { - page: 1, - per_page: numberOfExecutions ?? 60, - filter: 'event.provider: alerting AND event.action:execute', - sort: [{ sort_field: '@timestamp', sort_order: 'desc' }], - end: dateNow.toISOString(), - }, - rule.legacyId !== null ? [rule.legacyId] : undefined - ), - ]); - events = queryResults.data; - executionEvents = executionResults.data; - } catch (err) { - this.logger.debug( - `rulesClient.getAlertSummary(): error searching event log for rule ${id}: ${err.message}` - ); - events = []; - executionEvents = []; - } - - return alertSummaryFromEventLog({ - rule, - events, - executionEvents, - dateStart: parsedDateStart.toISOString(), - dateEnd: dateNow.toISOString(), - }); - } - - public async getExecutionLogForRule({ - id, - dateStart, - dateEnd, - filter, - page, - perPage, - sort, - }: GetExecutionLogByIdParams): Promise { - this.logger.debug(`getExecutionLogForRule(): getting execution log for rule ${id}`); - const rule = (await this.get({ id, includeLegacyId: true })) as SanitizedRuleWithLegacyId; - - try { - // Make sure user has access to this rule - await this.authorization.ensureAuthorized({ - ruleTypeId: rule.alertTypeId, - consumer: rule.consumer, - operation: ReadOperations.GetExecutionLog, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_EXECUTION_LOG, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_EXECUTION_LOG, - savedObject: { type: 'alert', id }, - }) - ); - - // default duration of instance summary is 60 * rule interval - const dateNow = new Date(); - const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); - const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); - - const eventLogClient = await this.getEventLogClient(); - - try { - const aggResult = await eventLogClient.aggregateEventsBySavedObjectIds( - 'alert', - [id], - { - start: parsedDateStart.toISOString(), - end: parsedDateEnd.toISOString(), - aggs: getExecutionLogAggregation({ - filter, - page, - perPage, - sort, - }), - }, - rule.legacyId !== null ? [rule.legacyId] : undefined - ); - - return formatExecutionLogResult(aggResult); - } catch (err) { - this.logger.debug( - `rulesClient.getExecutionLogForRule(): error searching event log for rule ${id}: ${err.message}` - ); - throw err; - } - } - - public async getGlobalExecutionLogWithAuth({ - dateStart, - dateEnd, - filter, - page, - perPage, - sort, - namespaces, - }: GetGlobalExecutionLogParams): Promise { - this.logger.debug(`getGlobalExecutionLogWithAuth(): getting global execution log`); - - let authorizationTuple; - try { - authorizationTuple = await this.authorization.getFindAuthorizationFilter( - AlertingAuthorizationEntity.Alert, - { - type: AlertingAuthorizationFilterType.KQL, - fieldNames: { - ruleTypeId: 'kibana.alert.rule.rule_type_id', - consumer: 'kibana.alert.rule.consumer', - }, - } - ); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_GLOBAL_EXECUTION_LOG, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_GLOBAL_EXECUTION_LOG, - }) - ); - - const dateNow = new Date(); - const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); - const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); - - const eventLogClient = await this.getEventLogClient(); - - try { - const aggResult = await eventLogClient.aggregateEventsWithAuthFilter( - 'alert', - authorizationTuple.filter as KueryNode, - { - start: parsedDateStart.toISOString(), - end: parsedDateEnd.toISOString(), - aggs: getExecutionLogAggregation({ - filter, - page, - perPage, - sort, - }), - }, - namespaces - ); - - return formatExecutionLogResult(aggResult); - } catch (err) { - this.logger.debug( - `rulesClient.getGlobalExecutionLogWithAuth(): error searching global event log: ${err.message}` - ); - throw err; - } - } - - public async getActionErrorLog({ - id, - dateStart, - dateEnd, - filter, - page, - perPage, - sort, - }: GetActionErrorLogByIdParams): Promise { - this.logger.debug(`getActionErrorLog(): getting action error logs for rule ${id}`); - const rule = (await this.get({ id, includeLegacyId: true })) as SanitizedRuleWithLegacyId; - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: rule.alertTypeId, - consumer: rule.consumer, - operation: ReadOperations.GetActionErrorLog, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_ACTION_ERROR_LOG, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_ACTION_ERROR_LOG, - savedObject: { type: 'alert', id }, - }) - ); - - // default duration of instance summary is 60 * rule interval - const dateNow = new Date(); - const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); - const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); - - const eventLogClient = await this.getEventLogClient(); - - try { - const errorResult = await eventLogClient.findEventsBySavedObjectIds( - 'alert', - [id], - { - start: parsedDateStart.toISOString(), - end: parsedDateEnd.toISOString(), - page, - per_page: perPage, - filter: filter - ? `(${actionErrorLogDefaultFilter}) AND (${filter})` - : actionErrorLogDefaultFilter, - sort: convertEsSortToEventLogSort(sort), - }, - rule.legacyId !== null ? [rule.legacyId] : undefined - ); - return formatExecutionErrorsResult(errorResult); - } catch (err) { - this.logger.debug( - `rulesClient.getActionErrorLog(): error searching event log for rule ${id}: ${err.message}` - ); - throw err; - } - } - - public async getActionErrorLogWithAuth({ - id, - dateStart, - dateEnd, - filter, - page, - perPage, - sort, - namespace, - }: GetActionErrorLogByIdParams): Promise { - this.logger.debug(`getActionErrorLogWithAuth(): getting action error logs for rule ${id}`); - - let authorizationTuple; - try { - authorizationTuple = await this.authorization.getFindAuthorizationFilter( - AlertingAuthorizationEntity.Alert, - { - type: AlertingAuthorizationFilterType.KQL, - fieldNames: { - ruleTypeId: 'kibana.alert.rule.rule_type_id', - consumer: 'kibana.alert.rule.consumer', - }, - } - ); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_ACTION_ERROR_LOG, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_ACTION_ERROR_LOG, - savedObject: { type: 'alert', id }, - }) - ); - - // default duration of instance summary is 60 * rule interval - const dateNow = new Date(); - const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); - const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); - - const eventLogClient = await this.getEventLogClient(); - - try { - const errorResult = await eventLogClient.findEventsWithAuthFilter( - 'alert', - [id], - authorizationTuple.filter as KueryNode, - namespace, - { - start: parsedDateStart.toISOString(), - end: parsedDateEnd.toISOString(), - page, - per_page: perPage, - filter: filter - ? `(${actionErrorLogDefaultFilter}) AND (${filter})` - : actionErrorLogDefaultFilter, - sort: convertEsSortToEventLogSort(sort), - } - ); - return formatExecutionErrorsResult(errorResult); - } catch (err) { - this.logger.debug( - `rulesClient.getActionErrorLog(): error searching event log for rule ${id}: ${err.message}` - ); - throw err; - } - } - - public async getGlobalExecutionKpiWithAuth({ - dateStart, - dateEnd, - filter, - namespaces, - }: GetGlobalExecutionKPIParams) { - this.logger.debug(`getGlobalExecutionLogWithAuth(): getting global execution log`); - - let authorizationTuple; - try { - authorizationTuple = await this.authorization.getFindAuthorizationFilter( - AlertingAuthorizationEntity.Alert, - { - type: AlertingAuthorizationFilterType.KQL, - fieldNames: { - ruleTypeId: 'kibana.alert.rule.rule_type_id', - consumer: 'kibana.alert.rule.consumer', - }, - } - ); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_GLOBAL_EXECUTION_KPI, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_GLOBAL_EXECUTION_KPI, - }) - ); - - const dateNow = new Date(); - const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); - const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); - - const eventLogClient = await this.getEventLogClient(); - - try { - const aggResult = await eventLogClient.aggregateEventsWithAuthFilter( - 'alert', - authorizationTuple.filter as KueryNode, - { - start: parsedDateStart.toISOString(), - end: parsedDateEnd.toISOString(), - aggs: getExecutionKPIAggregation(filter), - }, - namespaces - ); - - return formatExecutionKPIResult(aggResult); - } catch (err) { - this.logger.debug( - `rulesClient.getGlobalExecutionKpiWithAuth(): error searching global execution KPI: ${err.message}` - ); - throw err; - } - } - - public async getRuleExecutionKPI({ id, dateStart, dateEnd, filter }: GetRuleExecutionKPIParams) { - this.logger.debug(`getRuleExecutionKPI(): getting execution KPI for rule ${id}`); - const rule = (await this.get({ id, includeLegacyId: true })) as SanitizedRuleWithLegacyId; - - try { - // Make sure user has access to this rule - await this.authorization.ensureAuthorized({ - ruleTypeId: rule.alertTypeId, - consumer: rule.consumer, - operation: ReadOperations.GetRuleExecutionKPI, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_RULE_EXECUTION_KPI, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.GET_RULE_EXECUTION_KPI, - savedObject: { type: 'alert', id }, - }) - ); - - // default duration of instance summary is 60 * rule interval - const dateNow = new Date(); - const parsedDateStart = parseDate(dateStart, 'dateStart', dateNow); - const parsedDateEnd = parseDate(dateEnd, 'dateEnd', dateNow); - - const eventLogClient = await this.getEventLogClient(); - - try { - const aggResult = await eventLogClient.aggregateEventsBySavedObjectIds( - 'alert', - [id], - { - start: parsedDateStart.toISOString(), - end: parsedDateEnd.toISOString(), - aggs: getExecutionKPIAggregation(filter), - }, - rule.legacyId !== null ? [rule.legacyId] : undefined - ); - - return formatExecutionKPIResult(aggResult); - } catch (err) { - this.logger.debug( - `rulesClient.getRuleExecutionKPI(): error searching execution KPI for rule ${id}: ${err.message}` - ); - throw err; - } - } - - public async find({ - options: { fields, ...options } = {}, - excludeFromPublicApi = false, - includeSnoozeData = false, - }: { - options?: FindOptions; - excludeFromPublicApi?: boolean; - includeSnoozeData?: boolean; - } = {}): Promise> { - let authorizationTuple; - try { - authorizationTuple = await this.authorization.getFindAuthorizationFilter( - AlertingAuthorizationEntity.Rule, - alertingAuthorizationFilterOpts - ); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.FIND, - error, - }) - ); - throw error; - } - - const { filter: authorizationFilter, ensureRuleTypeIsAuthorized } = authorizationTuple; - - const filterKueryNode = buildKueryNodeFilter(options.filter); - let sortField = mapSortField(options.sortField); - if (excludeFromPublicApi) { - try { - validateOperationOnAttributes( - filterKueryNode, - sortField, - options.searchFields, - this.fieldsToExcludeFromPublicApi - ); - } catch (error) { - throw Boom.badRequest(`Error find rules: ${error.message}`); - } - } - - sortField = mapSortField(getModifiedField(options.sortField)); - - // Generate new modified search and search fields, translating certain params properties - // to mapped_params. Thus, allowing for sort/search/filtering on params. - // We do the modifcation after the validate check to make sure the public API does not - // use the mapped_params in their queries. - options = { - ...options, - ...(options.searchFields && { searchFields: getModifiedSearchFields(options.searchFields) }), - ...(options.search && { search: getModifiedSearch(options.searchFields, options.search) }), - }; - - // Modifies kuery node AST to translate params filter and the filter value to mapped_params. - // This translation is done in place, and therefore is not a pure function. - if (filterKueryNode) { - modifyFilterKueryNode({ astFilter: filterKueryNode }); - } - - const { - page, - per_page: perPage, - total, - saved_objects: data, - } = await this.unsecuredSavedObjectsClient.find({ - ...options, - sortField, - filter: - (authorizationFilter && filterKueryNode - ? nodeBuilder.and([filterKueryNode, authorizationFilter as KueryNode]) - : authorizationFilter) ?? filterKueryNode, - fields: fields ? this.includeFieldsRequiredForAuthentication(fields) : fields, - type: 'alert', - }); - - const authorizedData = data.map(({ id, attributes, references }) => { - try { - ensureRuleTypeIsAuthorized( - attributes.alertTypeId, - attributes.consumer, - AlertingAuthorizationEntity.Rule - ); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.FIND, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - return this.getAlertFromRaw( - id, - attributes.alertTypeId, - fields ? (pick(attributes, fields) as RawRule) : attributes, - references, - false, - excludeFromPublicApi, - includeSnoozeData - ); - }); - - authorizedData.forEach(({ id }) => - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.FIND, - savedObject: { type: 'alert', id }, - }) - ) - ); - - return { - page, - perPage, - total, - data: authorizedData, - }; - } - - public async aggregate({ - options: { fields, filter, ...options } = {}, - }: { options?: AggregateOptions } = {}): Promise { - let authorizationTuple; - try { - authorizationTuple = await this.authorization.getFindAuthorizationFilter( - AlertingAuthorizationEntity.Rule, - alertingAuthorizationFilterOpts - ); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.AGGREGATE, - error, - }) - ); - throw error; - } - - const { filter: authorizationFilter } = authorizationTuple; - const filterKueryNode = buildKueryNodeFilter(filter); - - const resp = await this.unsecuredSavedObjectsClient.find({ - ...options, - filter: - authorizationFilter && filterKueryNode - ? nodeBuilder.and([filterKueryNode, authorizationFilter as KueryNode]) - : authorizationFilter, - page: 1, - perPage: 0, - type: 'alert', - aggs: { - status: { - terms: { field: 'alert.attributes.executionStatus.status' }, - }, - outcome: { - terms: { field: 'alert.attributes.lastRun.outcome' }, - }, - enabled: { - terms: { field: 'alert.attributes.enabled' }, - }, - muted: { - terms: { field: 'alert.attributes.muteAll' }, - }, - tags: { - terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 }, - }, - snoozed: { - nested: { - path: 'alert.attributes.snoozeSchedule', - }, - aggs: { - count: { - filter: { - exists: { - field: 'alert.attributes.snoozeSchedule.duration', - }, - }, - }, - }, - }, - }, - }); - - if (!resp.aggregations) { - // Return a placeholder with all zeroes - const placeholder: AggregateResult = { - alertExecutionStatus: {}, - ruleLastRunOutcome: {}, - ruleEnabledStatus: { - enabled: 0, - disabled: 0, - }, - ruleMutedStatus: { - muted: 0, - unmuted: 0, - }, - ruleSnoozedStatus: { snoozed: 0 }, - }; - - for (const key of RuleExecutionStatusValues) { - placeholder.alertExecutionStatus[key] = 0; - } - - return placeholder; - } - - const alertExecutionStatus = resp.aggregations.status.buckets.map( - ({ key, doc_count: docCount }) => ({ - [key]: docCount, - }) - ); - - const ruleLastRunOutcome = resp.aggregations.outcome.buckets.map( - ({ key, doc_count: docCount }) => ({ - [key]: docCount, - }) - ); - - const ret: AggregateResult = { - alertExecutionStatus: alertExecutionStatus.reduce( - (acc, curr: { [status: string]: number }) => Object.assign(acc, curr), - {} - ), - ruleLastRunOutcome: ruleLastRunOutcome.reduce( - (acc, curr: { [status: string]: number }) => Object.assign(acc, curr), - {} - ), - }; - - // Fill missing keys with zeroes - for (const key of RuleExecutionStatusValues) { - if (!ret.alertExecutionStatus.hasOwnProperty(key)) { - ret.alertExecutionStatus[key] = 0; - } - } - for (const key of RuleLastRunOutcomeValues) { - if (!ret.ruleLastRunOutcome.hasOwnProperty(key)) { - ret.ruleLastRunOutcome[key] = 0; - } - } - - const enabledBuckets = resp.aggregations.enabled.buckets; - ret.ruleEnabledStatus = { - enabled: enabledBuckets.find((bucket) => bucket.key === 1)?.doc_count ?? 0, - disabled: enabledBuckets.find((bucket) => bucket.key === 0)?.doc_count ?? 0, - }; - - const mutedBuckets = resp.aggregations.muted.buckets; - ret.ruleMutedStatus = { - muted: mutedBuckets.find((bucket) => bucket.key === 1)?.doc_count ?? 0, - unmuted: mutedBuckets.find((bucket) => bucket.key === 0)?.doc_count ?? 0, - }; - - ret.ruleSnoozedStatus = { - snoozed: resp.aggregations.snoozed?.count?.doc_count ?? 0, - }; - - const tagsBuckets = resp.aggregations.tags?.buckets || []; - ret.ruleTags = tagsBuckets.map((bucket) => bucket.key); - - return ret; - } - - public async delete({ id }: { id: string }) { - return await retryIfConflicts( - this.logger, - `rulesClient.delete('${id}')`, - async () => await this.deleteWithOCC({ id }) - ); - } - - private async deleteWithOCC({ id }: { id: string }) { - let taskIdToRemove: string | undefined | null; - let apiKeyToInvalidate: string | null = null; - let attributes: RawRule; - - try { - const decryptedAlert = - await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser('alert', id, { - namespace: this.namespace, - }); - apiKeyToInvalidate = decryptedAlert.attributes.apiKey; - taskIdToRemove = decryptedAlert.attributes.scheduledTaskId; - attributes = decryptedAlert.attributes; - } catch (e) { - // We'll skip invalidating the API key since we failed to load the decrypted saved object - this.logger.error( - `delete(): Failed to load API key to invalidate on alert ${id}: ${e.message}` - ); - // Still attempt to load the scheduledTaskId using SOC - const alert = await this.unsecuredSavedObjectsClient.get('alert', id); - taskIdToRemove = alert.attributes.scheduledTaskId; - attributes = alert.attributes; - } - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: WriteOperations.Delete, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.DELETE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.DELETE, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - const removeResult = await this.unsecuredSavedObjectsClient.delete('alert', id); - - await Promise.all([ - taskIdToRemove ? this.taskManager.removeIfExists(taskIdToRemove) : null, - apiKeyToInvalidate - ? bulkMarkApiKeysForInvalidation( - { apiKeys: [apiKeyToInvalidate] }, - this.logger, - this.unsecuredSavedObjectsClient - ) - : null, - ]); - - return removeResult; - } - - public async update({ - id, - data, - }: UpdateOptions): Promise> { - return await retryIfConflicts( - this.logger, - `rulesClient.update('${id}')`, - async () => await this.updateWithOCC({ id, data }) - ); - } - - private async updateWithOCC({ - id, - data, - }: UpdateOptions): Promise> { - let alertSavedObject: SavedObject; - - try { - alertSavedObject = await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser( - 'alert', - id, - { - namespace: this.namespace, - } - ); - } catch (e) { - // We'll skip invalidating the API key since we failed to load the decrypted saved object - this.logger.error( - `update(): Failed to load API key to invalidate on alert ${id}: ${e.message}` - ); - // Still attempt to load the object using SOC - alertSavedObject = await this.unsecuredSavedObjectsClient.get('alert', id); - } - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: alertSavedObject.attributes.alertTypeId, - consumer: alertSavedObject.attributes.consumer, - operation: WriteOperations.Update, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.UPDATE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.UPDATE, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(alertSavedObject.attributes.alertTypeId); - - const updateResult = await this.updateAlert({ id, data }, alertSavedObject); - - await Promise.all([ - alertSavedObject.attributes.apiKey - ? bulkMarkApiKeysForInvalidation( - { apiKeys: [alertSavedObject.attributes.apiKey] }, - this.logger, - this.unsecuredSavedObjectsClient - ) - : null, - (async () => { - if ( - updateResult.scheduledTaskId && - updateResult.schedule && - !isEqual(alertSavedObject.attributes.schedule, updateResult.schedule) - ) { - try { - const { tasks } = await this.taskManager.bulkUpdateSchedules( - [updateResult.scheduledTaskId], - updateResult.schedule - ); - - this.logger.debug( - `Rule update has rescheduled the underlying task: ${updateResult.scheduledTaskId} to run at: ${tasks?.[0]?.runAt}` - ); - } catch (err) { - this.logger.error( - `Rule update failed to run its underlying task. TaskManager bulkUpdateSchedules failed with Error: ${err.message}` - ); - } - } - })(), - ]); - - return updateResult; - } - - private async updateAlert( - { id, data }: UpdateOptions, - { attributes, version }: SavedObject - ): Promise> { - const ruleType = this.ruleTypeRegistry.get(attributes.alertTypeId); - - // Validate - const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate?.params); - await this.validateActions(ruleType, data); - - // Throw error if schedule interval is less than the minimum and we are enforcing it - const intervalInMs = parseDuration(data.schedule.interval); - if (intervalInMs < this.minimumScheduleIntervalInMs && this.minimumScheduleInterval.enforce) { - throw Boom.badRequest( - `Error updating rule: the interval is less than the allowed minimum interval of ${this.minimumScheduleInterval.value}` - ); - } - - // Extract saved object references for this rule - const { - references, - params: updatedParams, - actions, - } = await this.extractReferences(ruleType, data.actions, validatedAlertTypeParams); - - const username = await this.getUserName(); - - let createdAPIKey = null; - try { - createdAPIKey = attributes.enabled - ? await this.createAPIKey(this.generateAPIKeyName(ruleType.id, data.name)) - : null; - } catch (error) { - throw Boom.badRequest(`Error updating rule: could not create API key - ${error.message}`); - } - - const apiKeyAttributes = this.apiKeyAsAlertAttributes(createdAPIKey, username); - const notifyWhen = getRuleNotifyWhenType(data.notifyWhen ?? null, data.throttle ?? null); - - let updatedObject: SavedObject; - const createAttributes = this.updateMeta({ - ...attributes, - ...data, - ...apiKeyAttributes, - params: updatedParams as RawRule['params'], - actions, - notifyWhen, - updatedBy: username, - updatedAt: new Date().toISOString(), - }); - - const mappedParams = getMappedParams(updatedParams); - - if (Object.keys(mappedParams).length) { - createAttributes.mapped_params = mappedParams; - } - - try { - updatedObject = await this.unsecuredSavedObjectsClient.create( - 'alert', - createAttributes, - { - id, - overwrite: true, - version, - references, - } - ); - } catch (e) { - // Avoid unused API key - await bulkMarkApiKeysForInvalidation( - { apiKeys: createAttributes.apiKey ? [createAttributes.apiKey] : [] }, - this.logger, - this.unsecuredSavedObjectsClient - ); - - throw e; - } - - // Log warning if schedule interval is less than the minimum but we're not enforcing it - if (intervalInMs < this.minimumScheduleIntervalInMs && !this.minimumScheduleInterval.enforce) { - this.logger.warn( - `Rule schedule interval (${data.schedule.interval}) for "${ruleType.id}" rule type with ID "${id}" is less than the minimum value (${this.minimumScheduleInterval.value}). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent such changes.` - ); - } - - return this.getPartialRuleFromRaw( - id, - ruleType, - updatedObject.attributes, - updatedObject.references, - false, - true - ); - } - - private getAuthorizationFilter = async ({ action }: { action: BulkAction }) => { - try { - const authorizationTuple = await this.authorization.getFindAuthorizationFilter( - AlertingAuthorizationEntity.Rule, - alertingAuthorizationFilterOpts - ); - return authorizationTuple.filter; - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction[action], - error, - }) - ); - throw error; - } - }; - - private getAndValidateCommonBulkOptions = (options: BulkOptions) => { - const filter = (options as BulkOptionsFilter).filter; - const ids = (options as BulkOptionsIds).ids; - - if (!ids && !filter) { - throw Boom.badRequest( - "Either 'ids' or 'filter' property in method's arguments should be provided" - ); - } - - if (ids?.length === 0) { - throw Boom.badRequest("'ids' property should not be an empty array"); - } - - if (ids && filter) { - throw Boom.badRequest( - "Both 'filter' and 'ids' are supplied. Define either 'ids' or 'filter' properties in method's arguments" - ); - } - return { ids, filter }; - }; - - private checkAuthorizationAndGetTotal = async ({ - filter, - action, - }: { - filter: KueryNode | null; - action: BulkAction; - }) => { - const actionToConstantsMapping: Record< - BulkAction, - { WriteOperation: WriteOperations | ReadOperations; RuleAuditAction: RuleAuditAction } - > = { - DELETE: { - WriteOperation: WriteOperations.BulkDelete, - RuleAuditAction: RuleAuditAction.DELETE, - }, - ENABLE: { - WriteOperation: WriteOperations.BulkEnable, - RuleAuditAction: RuleAuditAction.ENABLE, - }, - DISABLE: { - WriteOperation: WriteOperations.BulkDisable, - RuleAuditAction: RuleAuditAction.DISABLE, - }, - }; - const { aggregations, total } = await this.unsecuredSavedObjectsClient.find< - RawRule, - RuleBulkOperationAggregation - >({ - filter, - page: 1, - perPage: 0, - type: 'alert', - aggs: { - alertTypeId: { - multi_terms: { - terms: [ - { field: 'alert.attributes.alertTypeId' }, - { field: 'alert.attributes.consumer' }, - ], - }, - }, - }, - }); - - if (total > MAX_RULES_NUMBER_FOR_BULK_OPERATION) { - throw Boom.badRequest( - `More than ${MAX_RULES_NUMBER_FOR_BULK_OPERATION} rules matched for bulk ${action.toLocaleLowerCase()}` - ); - } - - const buckets = aggregations?.alertTypeId.buckets; - - if (buckets === undefined || buckets?.length === 0) { - throw Boom.badRequest(`No rules found for bulk ${action.toLocaleLowerCase()}`); - } - - await pMap( - buckets, - async ({ key: [ruleType, consumer, actions] }) => { - this.ruleTypeRegistry.ensureRuleTypeEnabled(ruleType); - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: ruleType, - consumer, - operation: actionToConstantsMapping[action].WriteOperation, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: actionToConstantsMapping[action].RuleAuditAction, - error, - }) - ); - throw error; - } - }, - { concurrency: RULE_TYPE_CHECKS_CONCURRENCY } - ); - return { total }; - }; - - public bulkDeleteRules = async (options: BulkOptions) => { - const { ids, filter } = this.getAndValidateCommonBulkOptions(options); - - const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); - const authorizationFilter = await this.getAuthorizationFilter({ action: 'DELETE' }); - - const kueryNodeFilterWithAuth = - authorizationFilter && kueryNodeFilter - ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) - : kueryNodeFilter; - - const { total } = await this.checkAuthorizationAndGetTotal({ - filter: kueryNodeFilterWithAuth, - action: 'DELETE', - }); - - const { apiKeysToInvalidate, errors, taskIdsToDelete } = await retryIfBulkDeleteConflicts( - this.logger, - (filterKueryNode: KueryNode | null) => this.bulkDeleteWithOCC({ filter: filterKueryNode }), - kueryNodeFilterWithAuth - ); - - const taskIdsFailedToBeDeleted: string[] = []; - const taskIdsSuccessfullyDeleted: string[] = []; - if (taskIdsToDelete.length > 0) { - try { - const resultFromDeletingTasks = await this.taskManager.bulkRemoveIfExist(taskIdsToDelete); - resultFromDeletingTasks?.statuses.forEach((status) => { - if (status.success) { - taskIdsSuccessfullyDeleted.push(status.id); - } else { - taskIdsFailedToBeDeleted.push(status.id); - } - }); - if (taskIdsSuccessfullyDeleted.length) { - this.logger.debug( - `Successfully deleted schedules for underlying tasks: ${taskIdsSuccessfullyDeleted.join( - ', ' - )}` - ); - } - if (taskIdsFailedToBeDeleted.length) { - this.logger.error( - `Failure to delete schedules for underlying tasks: ${taskIdsFailedToBeDeleted.join( - ', ' - )}` - ); - } - } catch (error) { - this.logger.error( - `Failure to delete schedules for underlying tasks: ${taskIdsToDelete.join( - ', ' - )}. TaskManager bulkRemoveIfExist failed with Error: ${error.message}` - ); - } - } - - await bulkMarkApiKeysForInvalidation( - { apiKeys: apiKeysToInvalidate }, - this.logger, - this.unsecuredSavedObjectsClient - ); - - return { errors, total, taskIdsFailedToBeDeleted }; - }; - - private bulkDeleteWithOCC = async ({ filter }: { filter: KueryNode | null }) => { - const rulesFinder = - await this.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( - { - filter, - type: 'alert', - perPage: 100, - ...(this.namespace ? { namespaces: [this.namespace] } : undefined), - } - ); - - const rules: SavedObjectsBulkDeleteObject[] = []; - const apiKeysToInvalidate: string[] = []; - const taskIdsToDelete: string[] = []; - const errors: BulkOperationError[] = []; - const apiKeyToRuleIdMapping: Record = {}; - const taskIdToRuleIdMapping: Record = {}; - const ruleNameToRuleIdMapping: Record = {}; - - for await (const response of rulesFinder.find()) { - for (const rule of response.saved_objects) { - if (rule.attributes.apiKey) { - apiKeyToRuleIdMapping[rule.id] = rule.attributes.apiKey; - } - if (rule.attributes.name) { - ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; - } - if (rule.attributes.scheduledTaskId) { - taskIdToRuleIdMapping[rule.id] = rule.attributes.scheduledTaskId; - } - rules.push(rule); - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.DELETE, - outcome: 'unknown', - savedObject: { type: 'alert', id: rule.id }, - }) - ); - } - } - - const result = await this.unsecuredSavedObjectsClient.bulkDelete(rules); - - result.statuses.forEach((status) => { - if (status.error === undefined) { - if (apiKeyToRuleIdMapping[status.id]) { - apiKeysToInvalidate.push(apiKeyToRuleIdMapping[status.id]); - } - if (taskIdToRuleIdMapping[status.id]) { - taskIdsToDelete.push(taskIdToRuleIdMapping[status.id]); - } - } else { - errors.push({ - message: status.error.message ?? 'n/a', - status: status.error.statusCode, - rule: { - id: status.id, - name: ruleNameToRuleIdMapping[status.id] ?? 'n/a', - }, - }); - } - }); - return { apiKeysToInvalidate, errors, taskIdsToDelete }; - }; - - public async bulkEdit( - options: BulkEditOptions - ): Promise<{ - rules: Array>; - errors: BulkOperationError[]; - total: number; - }> { - const queryFilter = (options as BulkEditOptionsFilter).filter; - const ids = (options as BulkEditOptionsIds).ids; - - if (ids && queryFilter) { - throw Boom.badRequest( - "Both 'filter' and 'ids' are supplied. Define either 'ids' or 'filter' properties in method arguments" - ); - } - - const qNodeQueryFilter = buildKueryNodeFilter(queryFilter); - - const qNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : qNodeQueryFilter; - let authorizationTuple; - try { - authorizationTuple = await this.authorization.getFindAuthorizationFilter( - AlertingAuthorizationEntity.Rule, - alertingAuthorizationFilterOpts - ); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.BULK_EDIT, - error, - }) - ); - throw error; - } - const { filter: authorizationFilter } = authorizationTuple; - const qNodeFilterWithAuth = - authorizationFilter && qNodeFilter - ? nodeBuilder.and([qNodeFilter, authorizationFilter as KueryNode]) - : qNodeFilter; - - const { aggregations, total } = await this.unsecuredSavedObjectsClient.find< - RawRule, - RuleBulkOperationAggregation - >({ - filter: qNodeFilterWithAuth, - page: 1, - perPage: 0, - type: 'alert', - aggs: { - alertTypeId: { - multi_terms: { - terms: [ - { field: 'alert.attributes.alertTypeId' }, - { field: 'alert.attributes.consumer' }, - ], - }, - }, - }, - }); - - if (total > MAX_RULES_NUMBER_FOR_BULK_OPERATION) { - throw Boom.badRequest( - `More than ${MAX_RULES_NUMBER_FOR_BULK_OPERATION} rules matched for bulk edit` - ); - } - const buckets = aggregations?.alertTypeId.buckets; - - if (buckets === undefined) { - throw Error('No rules found for bulk edit'); - } - - await pMap( - buckets, - async ({ key: [ruleType, consumer] }) => { - this.ruleTypeRegistry.ensureRuleTypeEnabled(ruleType); - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: ruleType, - consumer, - operation: WriteOperations.BulkEdit, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.BULK_EDIT, - error, - }) - ); - throw error; - } - }, - { concurrency: RULE_TYPE_CHECKS_CONCURRENCY } - ); - - const { apiKeysToInvalidate, results, errors } = await retryIfBulkEditConflicts( - this.logger, - `rulesClient.update('operations=${JSON.stringify(options.operations)}, paramsModifier=${ - options.paramsModifier ? '[Function]' : undefined - }')`, - (filterKueryNode: KueryNode | null) => - this.bulkEditOcc({ - filter: filterKueryNode, - operations: options.operations, - paramsModifier: options.paramsModifier, - }), - qNodeFilterWithAuth - ); - - await bulkMarkApiKeysForInvalidation( - { apiKeys: apiKeysToInvalidate }, - this.logger, - this.unsecuredSavedObjectsClient - ); - - const updatedRules = results.map(({ id, attributes, references }) => { - return this.getAlertFromRaw( - id, - attributes.alertTypeId as string, - attributes as RawRule, - references, - false - ); - }); - - // update schedules only if schedule operation is present - const scheduleOperation = options.operations.find( - ( - operation - ): operation is Extract }> => - operation.field === 'schedule' - ); - - if (scheduleOperation?.value) { - const taskIds = updatedRules.reduce((acc, rule) => { - if (rule.scheduledTaskId) { - acc.push(rule.scheduledTaskId); - } - return acc; - }, []); - - try { - await this.taskManager.bulkUpdateSchedules(taskIds, scheduleOperation.value); - this.logger.debug( - `Successfully updated schedules for underlying tasks: ${taskIds.join(', ')}` - ); - } catch (error) { - this.logger.error( - `Failure to update schedules for underlying tasks: ${taskIds.join( - ', ' - )}. TaskManager bulkUpdateSchedules failed with Error: ${error.message}` - ); - } - } - - return { rules: updatedRules, errors, total }; - } - - private async bulkEditOcc({ - filter, - operations, - paramsModifier, - }: { - filter: KueryNode | null; - operations: BulkEditOptions['operations']; - paramsModifier: BulkEditOptions['paramsModifier']; - }): Promise<{ - apiKeysToInvalidate: string[]; - rules: Array>; - resultSavedObjects: Array>; - errors: BulkOperationError[]; - }> { - const rulesFinder = - await this.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( - { - filter, - type: 'alert', - perPage: 100, - ...(this.namespace ? { namespaces: [this.namespace] } : undefined), - } - ); - - const rules: Array> = []; - const errors: BulkOperationError[] = []; - const apiKeysToInvalidate: string[] = []; - const apiKeysMap = new Map(); - const username = await this.getUserName(); - - for await (const response of rulesFinder.find()) { - await pMap( - response.saved_objects, - async (rule) => { - try { - if (rule.attributes.apiKey) { - apiKeysMap.set(rule.id, { oldApiKey: rule.attributes.apiKey }); - } - - const ruleType = this.ruleTypeRegistry.get(rule.attributes.alertTypeId); - - let attributes = cloneDeep(rule.attributes); - let ruleActions = { - actions: this.injectReferencesIntoActions( - rule.id, - rule.attributes.actions, - rule.references || [] - ), - }; - - for (const operation of operations) { - const { field } = operation; - if (field === 'snoozeSchedule' || field === 'apiKey') { - if (rule.attributes.actions.length) { - try { - await this.actionsAuthorization.ensureAuthorized('execute'); - } catch (error) { - throw Error(`Rule not authorized for bulk ${field} update - ${error.message}`); - } - } - } - } - - let hasUpdateApiKeyOperation = false; - - for (const operation of operations) { - switch (operation.field) { - case 'actions': - await this.validateActions(ruleType, { ...attributes, actions: operation.value }); - ruleActions = applyBulkEditOperation(operation, ruleActions); - break; - case 'snoozeSchedule': - // Silently skip adding snooze or snooze schedules on security - // rules until we implement snoozing of their rules - if (attributes.consumer === AlertConsumers.SIEM) { - break; - } - if (operation.operation === 'set') { - const snoozeAttributes = getBulkSnoozeAttributes(attributes, operation.value); - try { - verifySnoozeScheduleLimit(snoozeAttributes); - } catch (error) { - throw Error(`Error updating rule: could not add snooze - ${error.message}`); - } - attributes = { - ...attributes, - ...snoozeAttributes, - }; - } - if (operation.operation === 'delete') { - const idsToDelete = operation.value && [...operation.value]; - if (idsToDelete?.length === 0) { - attributes.snoozeSchedule?.forEach((schedule) => { - if (schedule.id) { - idsToDelete.push(schedule.id); - } - }); - } - attributes = { - ...attributes, - ...getBulkUnsnoozeAttributes(attributes, idsToDelete), - }; - } - break; - case 'apiKey': { - hasUpdateApiKeyOperation = true; - break; - } - default: - attributes = applyBulkEditOperation(operation, attributes); - } - } - - // validate schedule interval - if (attributes.schedule.interval) { - const isIntervalInvalid = - parseDuration(attributes.schedule.interval as string) < - this.minimumScheduleIntervalInMs; - if (isIntervalInvalid && this.minimumScheduleInterval.enforce) { - throw Error( - `Error updating rule: the interval is less than the allowed minimum interval of ${this.minimumScheduleInterval.value}` - ); - } else if (isIntervalInvalid && !this.minimumScheduleInterval.enforce) { - this.logger.warn( - `Rule schedule interval (${attributes.schedule.interval}) for "${ruleType.id}" rule type with ID "${attributes.id}" is less than the minimum value (${this.minimumScheduleInterval.value}). Running rules at this interval may impact alerting performance. Set "xpack.alerting.rules.minimumScheduleInterval.enforce" to true to prevent such changes.` - ); - } - } - - const ruleParams = paramsModifier - ? await paramsModifier(attributes.params as Params) - : attributes.params; - - // validate rule params - const validatedAlertTypeParams = validateRuleTypeParams( - ruleParams, - ruleType.validate?.params - ); - const validatedMutatedAlertTypeParams = validateMutatedRuleTypeParams( - validatedAlertTypeParams, - rule.attributes.params, - ruleType.validate?.params - ); - - const { - actions: rawAlertActions, - references, - params: updatedParams, - } = await this.extractReferences( - ruleType, - ruleActions.actions, - validatedMutatedAlertTypeParams - ); - - const shouldUpdateApiKey = attributes.enabled || hasUpdateApiKeyOperation; - - // create API key - let createdAPIKey = null; - try { - createdAPIKey = shouldUpdateApiKey - ? await this.createAPIKey(this.generateAPIKeyName(ruleType.id, attributes.name)) - : null; - } catch (error) { - throw Error(`Error updating rule: could not create API key - ${error.message}`); - } - - const apiKeyAttributes = this.apiKeyAsAlertAttributes(createdAPIKey, username); - - // collect generated API keys - if (apiKeyAttributes.apiKey) { - apiKeysMap.set(rule.id, { - ...apiKeysMap.get(rule.id), - newApiKey: apiKeyAttributes.apiKey, - }); - } - - // get notifyWhen - const notifyWhen = getRuleNotifyWhenType( - attributes.notifyWhen ?? null, - attributes.throttle ?? null - ); - - const updatedAttributes = this.updateMeta({ - ...attributes, - ...apiKeyAttributes, - params: updatedParams as RawRule['params'], - actions: rawAlertActions, - notifyWhen, - updatedBy: username, - updatedAt: new Date().toISOString(), - }); - - // add mapped_params - const mappedParams = getMappedParams(updatedParams); - - if (Object.keys(mappedParams).length) { - updatedAttributes.mapped_params = mappedParams; - } - - rules.push({ - ...rule, - references, - attributes: updatedAttributes, - }); - } catch (error) { - errors.push({ - message: error.message, - rule: { - id: rule.id, - name: rule.attributes?.name, - }, - }); - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.BULK_EDIT, - error, - }) - ); - } - }, - { concurrency: API_KEY_GENERATE_CONCURRENCY } - ); - } - - let result; - try { - result = await this.unsecuredSavedObjectsClient.bulkCreate(rules, { overwrite: true }); - } catch (e) { - // avoid unused newly generated API keys - if (apiKeysMap.size > 0) { - await bulkMarkApiKeysForInvalidation( - { - apiKeys: Array.from(apiKeysMap.values()).reduce((acc, value) => { - if (value.newApiKey) { - acc.push(value.newApiKey); - } - return acc; - }, []), - }, - this.logger, - this.unsecuredSavedObjectsClient - ); - } - throw e; - } - - result.saved_objects.map(({ id, error }) => { - const oldApiKey = apiKeysMap.get(id)?.oldApiKey; - const newApiKey = apiKeysMap.get(id)?.newApiKey; - - // if SO wasn't saved and has new API key it will be invalidated - if (error && newApiKey) { - apiKeysToInvalidate.push(newApiKey); - // if SO saved and has old Api Key it will be invalidate - } else if (!error && oldApiKey) { - apiKeysToInvalidate.push(oldApiKey); - } - }); - - return { apiKeysToInvalidate, resultSavedObjects: result.saved_objects, errors, rules }; - } - - private getShouldScheduleTask = async (scheduledTaskId: string | null | undefined) => { - if (!scheduledTaskId) return true; - try { - // make sure scheduledTaskId exist - await this.taskManager.get(scheduledTaskId); - return false; - } catch (err) { - return true; - } - }; - - public bulkEnableRules = async (options: BulkOptions) => { - const { ids, filter } = this.getAndValidateCommonBulkOptions(options); - - const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); - const authorizationFilter = await this.getAuthorizationFilter({ action: 'ENABLE' }); - - const kueryNodeFilterWithAuth = - authorizationFilter && kueryNodeFilter - ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) - : kueryNodeFilter; - - const { total } = await this.checkAuthorizationAndGetTotal({ - filter: kueryNodeFilterWithAuth, - action: 'ENABLE', - }); - - const { errors, rules, accListSpecificForBulkOperation } = await retryIfBulkOperationConflicts({ - action: 'ENABLE', - logger: this.logger, - bulkOperation: (filterKueryNode: KueryNode | null) => - this.bulkEnableRulesWithOCC({ filter: filterKueryNode }), - filter: kueryNodeFilterWithAuth, - }); - - const [taskIdsToEnable] = accListSpecificForBulkOperation; - - const taskIdsFailedToBeEnabled: string[] = []; - if (taskIdsToEnable.length > 0) { - try { - const resultFromEnablingTasks = await this.taskManager.bulkEnable(taskIdsToEnable); - resultFromEnablingTasks?.errors?.forEach((error) => { - taskIdsFailedToBeEnabled.push(error.task.id); - }); - if (resultFromEnablingTasks.tasks.length) { - this.logger.debug( - `Successfully enabled schedules for underlying tasks: ${resultFromEnablingTasks.tasks - .map((task) => task.id) - .join(', ')}` - ); - } - if (resultFromEnablingTasks.errors.length) { - this.logger.error( - `Failure to enable schedules for underlying tasks: ${resultFromEnablingTasks.errors - .map((error) => error.task.id) - .join(', ')}` - ); - } - } catch (error) { - taskIdsFailedToBeEnabled.push(...taskIdsToEnable); - this.logger.error( - `Failure to enable schedules for underlying tasks: ${taskIdsToEnable.join( - ', ' - )}. TaskManager bulkEnable failed with Error: ${error.message}` - ); - } - } - - const updatedRules = rules.map(({ id, attributes, references }) => { - return this.getAlertFromRaw( - id, - attributes.alertTypeId as string, - attributes as RawRule, - references, - false - ); - }); - - return { errors, rules: updatedRules, total, taskIdsFailedToBeEnabled }; - }; - - private bulkEnableRulesWithOCC = async ({ filter }: { filter: KueryNode | null }) => { - const rulesFinder = - await this.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( - { - filter, - type: 'alert', - perPage: 100, - ...(this.namespace ? { namespaces: [this.namespace] } : undefined), - } - ); - - const rulesToEnable: Array> = []; - const taskIdsToEnable: string[] = []; - const errors: BulkOperationError[] = []; - const ruleNameToRuleIdMapping: Record = {}; - - for await (const response of rulesFinder.find()) { - await pMap(response.saved_objects, async (rule) => { - try { - if (rule.attributes.actions.length) { - try { - await this.actionsAuthorization.ensureAuthorized('execute'); - } catch (error) { - throw Error(`Rule not authorized for bulk enable - ${error.message}`); - } - } - if (rule.attributes.enabled === true) return; - if (rule.attributes.name) { - ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; - } - - const username = await this.getUserName(); - - const updatedAttributes = this.updateMeta({ - ...rule.attributes, - ...(!rule.attributes.apiKey && - (await this.createNewAPIKeySet({ attributes: rule.attributes, username }))), - enabled: true, - updatedBy: username, - updatedAt: new Date().toISOString(), - executionStatus: { - status: 'pending', - lastDuration: 0, - lastExecutionDate: new Date().toISOString(), - error: null, - warning: null, - }, - }); - - const shouldScheduleTask = await this.getShouldScheduleTask( - rule.attributes.scheduledTaskId - ); - - let scheduledTaskId; - if (shouldScheduleTask) { - const scheduledTask = await this.scheduleTask({ - id: rule.id, - consumer: rule.attributes.consumer, - ruleTypeId: rule.attributes.alertTypeId, - schedule: rule.attributes.schedule as IntervalSchedule, - throwOnConflict: false, - }); - scheduledTaskId = scheduledTask.id; - } - - rulesToEnable.push({ - ...rule, - attributes: { - ...updatedAttributes, - ...(scheduledTaskId ? { scheduledTaskId } : undefined), - }, - }); - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.ENABLE, - outcome: 'unknown', - savedObject: { type: 'alert', id: rule.id }, - }) - ); - } catch (error) { - errors.push({ - message: error.message, - rule: { - id: rule.id, - name: rule.attributes?.name, - }, - }); - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.ENABLE, - error, - }) - ); - } - }); - } - - const result = await this.unsecuredSavedObjectsClient.bulkCreate(rulesToEnable, { - overwrite: true, - }); - - const rules: Array> = []; - - result.saved_objects.forEach((rule) => { - if (rule.error === undefined) { - if (rule.attributes.scheduledTaskId) { - taskIdsToEnable.push(rule.attributes.scheduledTaskId); - } - rules.push(rule); - } else { - errors.push({ - message: rule.error.message ?? 'n/a', - status: rule.error.statusCode, - rule: { - id: rule.id, - name: ruleNameToRuleIdMapping[rule.id] ?? 'n/a', - }, - }); - } - }); - return { errors, rules, accListSpecificForBulkOperation: [taskIdsToEnable] }; - }; - - private recoverRuleAlerts = async (id: string, attributes: RawRule) => { - if (!this.eventLogger || !attributes.scheduledTaskId) return; - try { - const { state } = taskInstanceToAlertTaskInstance( - await this.taskManager.get(attributes.scheduledTaskId), - attributes as unknown as SanitizedRule - ); - - const recoveredAlerts = mapValues, Alert>( - state.alertInstances ?? {}, - (rawAlertInstance, alertId) => new Alert(alertId, rawAlertInstance) - ); - const recoveredAlertIds = Object.keys(recoveredAlerts); - - for (const alertId of recoveredAlertIds) { - const { group: actionGroup } = recoveredAlerts[alertId].getLastScheduledActions() ?? {}; - const instanceState = recoveredAlerts[alertId].getState(); - const message = `instance '${alertId}' has recovered due to the rule was disabled`; - - const event = createAlertEventLogRecordObject({ - ruleId: id, - ruleName: attributes.name, - ruleType: this.ruleTypeRegistry.get(attributes.alertTypeId), - consumer: attributes.consumer, - instanceId: alertId, - action: EVENT_LOG_ACTIONS.recoveredInstance, - message, - state: instanceState, - group: actionGroup, - namespace: this.namespace, - spaceId: this.spaceId, - savedObjects: [ - { - id, - type: 'alert', - typeId: attributes.alertTypeId, - relation: SAVED_OBJECT_REL_PRIMARY, - }, - ], - }); - this.eventLogger.logEvent(event); - } - } catch (error) { - // this should not block the rest of the disable process - this.logger.warn( - `rulesClient.disable('${id}') - Could not write recovery events - ${error.message}` - ); - } - }; - - public bulkDisableRules = async (options: BulkOptions) => { - const { ids, filter } = this.getAndValidateCommonBulkOptions(options); - - const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); - const authorizationFilter = await this.getAuthorizationFilter({ action: 'DISABLE' }); - - const kueryNodeFilterWithAuth = - authorizationFilter && kueryNodeFilter - ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) - : kueryNodeFilter; - - const { total } = await this.checkAuthorizationAndGetTotal({ - filter: kueryNodeFilterWithAuth, - action: 'DISABLE', - }); - - const { errors, rules, taskIdsToDisable, taskIdsToDelete } = await retryIfBulkDisableConflicts( - this.logger, - (filterKueryNode: KueryNode | null) => - this.bulkDisableRulesWithOCC({ filter: filterKueryNode }), - kueryNodeFilterWithAuth - ); - - if (taskIdsToDisable.length > 0) { - try { - const resultFromDisablingTasks = await this.taskManager.bulkDisable(taskIdsToDisable); - if (resultFromDisablingTasks.tasks.length) { - this.logger.debug( - `Successfully disabled schedules for underlying tasks: ${resultFromDisablingTasks.tasks - .map((task) => task.id) - .join(', ')}` - ); - } - if (resultFromDisablingTasks.errors.length) { - this.logger.error( - `Failure to disable schedules for underlying tasks: ${resultFromDisablingTasks.errors - .map((error) => error.task.id) - .join(', ')}` - ); - } - } catch (error) { - this.logger.error( - `Failure to disable schedules for underlying tasks: ${taskIdsToDisable.join( - ', ' - )}. TaskManager bulkDisable failed with Error: ${error.message}` - ); - } - } - - const taskIdsFailedToBeDeleted: string[] = []; - const taskIdsSuccessfullyDeleted: string[] = []; - - if (taskIdsToDelete.length > 0) { - try { - const resultFromDeletingTasks = await this.taskManager.bulkRemoveIfExist(taskIdsToDelete); - resultFromDeletingTasks?.statuses.forEach((status) => { - if (status.success) { - taskIdsSuccessfullyDeleted.push(status.id); - } else { - taskIdsFailedToBeDeleted.push(status.id); - } - }); - if (taskIdsSuccessfullyDeleted.length) { - this.logger.debug( - `Successfully deleted schedules for underlying tasks: ${taskIdsSuccessfullyDeleted.join( - ', ' - )}` - ); - } - if (taskIdsFailedToBeDeleted.length) { - this.logger.error( - `Failure to delete schedules for underlying tasks: ${taskIdsFailedToBeDeleted.join( - ', ' - )}` - ); - } - } catch (error) { - this.logger.error( - `Failure to delete schedules for underlying tasks: ${taskIdsToDelete.join( - ', ' - )}. TaskManager bulkRemoveIfExist failed with Error: ${error.message}` - ); - } - } - - const updatedRules = rules.map(({ id, attributes, references }) => { - return this.getAlertFromRaw( - id, - attributes.alertTypeId as string, - attributes as RawRule, - references, - false - ); - }); - - return { errors, rules: updatedRules, total }; - }; - - private bulkDisableRulesWithOCC = async ({ filter }: { filter: KueryNode | null }) => { - const rulesFinder = - await this.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( - { - filter, - type: 'alert', - perPage: 100, - ...(this.namespace ? { namespaces: [this.namespace] } : undefined), - } - ); - - const rulesToDisable: Array> = []; - const errors: BulkOperationError[] = []; - const ruleNameToRuleIdMapping: Record = {}; - - for await (const response of rulesFinder.find()) { - await pMap(response.saved_objects, async (rule) => { - try { - if (rule.attributes.enabled === false) return; - - this.recoverRuleAlerts(rule.id, rule.attributes); - - if (rule.attributes.name) { - ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; - } - - const username = await this.getUserName(); - const updatedAttributes = this.updateMeta({ - ...rule.attributes, - enabled: false, - scheduledTaskId: - rule.attributes.scheduledTaskId === rule.id ? rule.attributes.scheduledTaskId : null, - updatedBy: username, - updatedAt: new Date().toISOString(), - }); - - rulesToDisable.push({ - ...rule, - attributes: { - ...updatedAttributes, - }, - }); - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.DISABLE, - outcome: 'unknown', - savedObject: { type: 'alert', id: rule.id }, - }) - ); - } catch (error) { - errors.push({ - message: error.message, - rule: { - id: rule.id, - name: rule.attributes?.name, - }, - }); - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.DISABLE, - error, - }) - ); - } - }); - } - - const result = await this.unsecuredSavedObjectsClient.bulkCreate(rulesToDisable, { - overwrite: true, - }); - - const taskIdsToDisable: string[] = []; - const taskIdsToDelete: string[] = []; - const disabledRules: Array> = []; - - result.saved_objects.forEach((rule) => { - if (rule.error === undefined) { - if (rule.attributes.scheduledTaskId) { - if (rule.attributes.scheduledTaskId !== rule.id) { - taskIdsToDelete.push(rule.attributes.scheduledTaskId); - } else { - taskIdsToDisable.push(rule.attributes.scheduledTaskId); - } - } - disabledRules.push(rule); - } else { - errors.push({ - message: rule.error.message ?? 'n/a', - status: rule.error.statusCode, - rule: { - id: rule.id, - name: ruleNameToRuleIdMapping[rule.id] ?? 'n/a', - }, - }); - } - }); - - return { errors, rules: disabledRules, taskIdsToDisable, taskIdsToDelete }; - }; - - private apiKeyAsAlertAttributes( - apiKey: CreateAPIKeyResult | null, - username: string | null - ): Pick { - return apiKey && apiKey.apiKeysEnabled - ? { - apiKeyOwner: username, - apiKey: Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64'), - } - : { - apiKeyOwner: null, - apiKey: null, - }; - } - - public async updateApiKey({ id }: { id: string }): Promise { - return await retryIfConflicts( - this.logger, - `rulesClient.updateApiKey('${id}')`, - async () => await this.updateApiKeyWithOCC({ id }) - ); - } - - private async updateApiKeyWithOCC({ id }: { id: string }) { - let apiKeyToInvalidate: string | null = null; - let attributes: RawRule; - let version: string | undefined; - - try { - const decryptedAlert = - await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser('alert', id, { - namespace: this.namespace, - }); - apiKeyToInvalidate = decryptedAlert.attributes.apiKey; - attributes = decryptedAlert.attributes; - version = decryptedAlert.version; - } catch (e) { - // We'll skip invalidating the API key since we failed to load the decrypted saved object - this.logger.error( - `updateApiKey(): Failed to load API key to invalidate on alert ${id}: ${e.message}` - ); - // Still attempt to load the attributes and version using SOC - const alert = await this.unsecuredSavedObjectsClient.get('alert', id); - attributes = alert.attributes; - version = alert.version; - } - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: WriteOperations.UpdateApiKey, - entity: AlertingAuthorizationEntity.Rule, - }); - if (attributes.actions.length) { - await this.actionsAuthorization.ensureAuthorized('execute'); - } - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.UPDATE_API_KEY, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - const username = await this.getUserName(); - - let createdAPIKey = null; - try { - createdAPIKey = await this.createAPIKey( - this.generateAPIKeyName(attributes.alertTypeId, attributes.name) - ); - } catch (error) { - throw Boom.badRequest( - `Error updating API key for rule: could not create API key - ${error.message}` - ); - } - - const updateAttributes = this.updateMeta({ - ...attributes, - ...this.apiKeyAsAlertAttributes(createdAPIKey, username), - updatedAt: new Date().toISOString(), - updatedBy: username, - }); - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.UPDATE_API_KEY, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - - try { - await this.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); - } catch (e) { - // Avoid unused API key - await bulkMarkApiKeysForInvalidation( - { apiKeys: updateAttributes.apiKey ? [updateAttributes.apiKey] : [] }, - this.logger, - this.unsecuredSavedObjectsClient - ); - throw e; - } - - if (apiKeyToInvalidate) { - await bulkMarkApiKeysForInvalidation( - { apiKeys: [apiKeyToInvalidate] }, - this.logger, - this.unsecuredSavedObjectsClient - ); - } - } - - public async enable({ id }: { id: string }): Promise { - return await retryIfConflicts( - this.logger, - `rulesClient.enable('${id}')`, - async () => await this.enableWithOCC({ id }) - ); - } - - private async enableWithOCC({ id }: { id: string }) { - let existingApiKey: string | null = null; - let attributes: RawRule; - let version: string | undefined; - - try { - const decryptedAlert = - await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser('alert', id, { - namespace: this.namespace, - }); - existingApiKey = decryptedAlert.attributes.apiKey; - attributes = decryptedAlert.attributes; - version = decryptedAlert.version; - } catch (e) { - this.logger.error(`enable(): Failed to load API key of alert ${id}: ${e.message}`); - // Still attempt to load the attributes and version using SOC - const alert = await this.unsecuredSavedObjectsClient.get('alert', id); - attributes = alert.attributes; - version = alert.version; - } - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: WriteOperations.Enable, - entity: AlertingAuthorizationEntity.Rule, - }); - - if (attributes.actions.length) { - await this.actionsAuthorization.ensureAuthorized('execute'); - } - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.ENABLE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.ENABLE, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - - if (attributes.enabled === false) { - const username = await this.getUserName(); - const now = new Date(); - - const schedule = attributes.schedule as IntervalSchedule; - - const updateAttributes = this.updateMeta({ - ...attributes, - ...(!existingApiKey && (await this.createNewAPIKeySet({ attributes, username }))), - ...(attributes.monitoring && { - monitoring: updateMonitoring({ - monitoring: attributes.monitoring, - timestamp: now.toISOString(), - duration: 0, - }), - }), - nextRun: getNextRun({ interval: schedule.interval }), - enabled: true, - updatedBy: username, - updatedAt: now.toISOString(), - executionStatus: { - status: 'pending', - lastDuration: 0, - lastExecutionDate: now.toISOString(), - error: null, - warning: null, - }, - }); - - try { - await this.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); - } catch (e) { - throw e; - } - } - - let scheduledTaskIdToCreate: string | null = null; - if (attributes.scheduledTaskId) { - // If scheduledTaskId defined in rule SO, make sure it exists - try { - await this.taskManager.get(attributes.scheduledTaskId); - } catch (err) { - scheduledTaskIdToCreate = id; - } - } else { - // If scheduledTaskId doesn't exist in rule SO, set it to rule ID - scheduledTaskIdToCreate = id; - } - - if (scheduledTaskIdToCreate) { - // Schedule the task if it doesn't exist - const scheduledTask = await this.scheduleTask({ - id, - consumer: attributes.consumer, - ruleTypeId: attributes.alertTypeId, - schedule: attributes.schedule as IntervalSchedule, - throwOnConflict: false, - }); - await this.unsecuredSavedObjectsClient.update('alert', id, { - scheduledTaskId: scheduledTask.id, - }); - } else { - // Task exists so set enabled to true - await this.taskManager.bulkEnable([attributes.scheduledTaskId!]); - } - } - - private async createNewAPIKeySet({ - attributes, - username, - }: { - attributes: RawRule; - username: string | null; - }): Promise> { - let createdAPIKey = null; - try { - createdAPIKey = await this.createAPIKey( - this.generateAPIKeyName(attributes.alertTypeId, attributes.name) - ); - } catch (error) { - throw Boom.badRequest(`Error creating API key for rule: ${error.message}`); - } - - return this.apiKeyAsAlertAttributes(createdAPIKey, username); - } - - public async disable({ id }: { id: string }): Promise { - return await retryIfConflicts( - this.logger, - `rulesClient.disable('${id}')`, - async () => await this.disableWithOCC({ id }) - ); - } - - private async disableWithOCC({ id }: { id: string }) { - let attributes: RawRule; - let version: string | undefined; - - try { - const decryptedAlert = - await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser('alert', id, { - namespace: this.namespace, - }); - attributes = decryptedAlert.attributes; - version = decryptedAlert.version; - } catch (e) { - this.logger.error(`disable(): Failed to load API key of alert ${id}: ${e.message}`); - // Still attempt to load the attributes and version using SOC - const alert = await this.unsecuredSavedObjectsClient.get('alert', id); - attributes = alert.attributes; - version = alert.version; - } - - this.recoverRuleAlerts(id, attributes); - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: WriteOperations.Disable, - entity: AlertingAuthorizationEntity.Rule, - }); - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.DISABLE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.DISABLE, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - - if (attributes.enabled === true) { - await this.unsecuredSavedObjectsClient.update( - 'alert', - id, - this.updateMeta({ - ...attributes, - enabled: false, - scheduledTaskId: attributes.scheduledTaskId === id ? attributes.scheduledTaskId : null, - updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), - nextRun: null, - }), - { version } - ); - - // If the scheduledTaskId does not match the rule id, we should - // remove the task, otherwise mark the task as disabled - if (attributes.scheduledTaskId) { - if (attributes.scheduledTaskId !== id) { - await this.taskManager.removeIfExists(attributes.scheduledTaskId); - } else { - await this.taskManager.bulkDisable([attributes.scheduledTaskId]); - } - } - } - } - - public async snooze({ - id, - snoozeSchedule, - }: { - id: string; - snoozeSchedule: RuleSnoozeSchedule; - }): Promise { - const snoozeDateValidationMsg = validateSnoozeStartDate(snoozeSchedule.rRule.dtstart); - if (snoozeDateValidationMsg) { - throw new RuleMutedError(snoozeDateValidationMsg); - } - - return await retryIfConflicts( - this.logger, - `rulesClient.snooze('${id}', ${JSON.stringify(snoozeSchedule, null, 4)})`, - async () => await this.snoozeWithOCC({ id, snoozeSchedule }) - ); - } - - private async snoozeWithOCC({ - id, - snoozeSchedule, - }: { - id: string; - snoozeSchedule: RuleSnoozeSchedule; - }) { - const { attributes, version } = await this.unsecuredSavedObjectsClient.get( - 'alert', - id - ); - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: WriteOperations.Snooze, - entity: AlertingAuthorizationEntity.Rule, - }); - - if (attributes.actions.length) { - await this.actionsAuthorization.ensureAuthorized('execute'); - } - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.SNOOZE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.SNOOZE, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - - const newAttrs = getSnoozeAttributes(attributes, snoozeSchedule); - - try { - verifySnoozeScheduleLimit(newAttrs); - } catch (error) { - throw Boom.badRequest(error.message); - } - - const updateAttributes = this.updateMeta({ - ...newAttrs, - updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), - }); - const updateOptions = { version }; - - await partiallyUpdateAlert( - this.unsecuredSavedObjectsClient, - id, - updateAttributes, - updateOptions - ); - } - - public async unsnooze({ - id, - scheduleIds, - }: { - id: string; - scheduleIds?: string[]; - }): Promise { - return await retryIfConflicts( - this.logger, - `rulesClient.unsnooze('${id}')`, - async () => await this.unsnoozeWithOCC({ id, scheduleIds }) - ); - } - - private async unsnoozeWithOCC({ id, scheduleIds }: { id: string; scheduleIds?: string[] }) { - const { attributes, version } = await this.unsecuredSavedObjectsClient.get( - 'alert', - id - ); - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: WriteOperations.Unsnooze, - entity: AlertingAuthorizationEntity.Rule, - }); - - if (attributes.actions.length) { - await this.actionsAuthorization.ensureAuthorized('execute'); - } - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.UNSNOOZE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.UNSNOOZE, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - const newAttrs = getUnsnoozeAttributes(attributes, scheduleIds); - - const updateAttributes = this.updateMeta({ - ...newAttrs, - updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), - }); - const updateOptions = { version }; - - await partiallyUpdateAlert( - this.unsecuredSavedObjectsClient, - id, - updateAttributes, - updateOptions - ); - } - - public calculateIsSnoozedUntil(rule: { - muteAll: boolean; - snoozeSchedule?: RuleSnooze; - }): string | null { - const isSnoozedUntil = getRuleSnoozeEndTime(rule); - return isSnoozedUntil ? isSnoozedUntil.toISOString() : null; - } - - public async clearExpiredSnoozes({ id }: { id: string }): Promise { - const { attributes, version } = await this.unsecuredSavedObjectsClient.get( - 'alert', - id - ); - - const snoozeSchedule = attributes.snoozeSchedule - ? attributes.snoozeSchedule.filter((s) => { - try { - return !isSnoozeExpired(s); - } catch (e) { - this.logger.error(`Error checking for expiration of snooze ${s.id}: ${e}`); - return true; - } - }) - : []; - - if (snoozeSchedule.length === attributes.snoozeSchedule?.length) return; - - const updateAttributes = this.updateMeta({ - snoozeSchedule, - updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), - }); - const updateOptions = { version }; - - await partiallyUpdateAlert( - this.unsecuredSavedObjectsClient, - id, - updateAttributes, - updateOptions - ); - } - - public async muteAll({ id }: { id: string }): Promise { - return await retryIfConflicts( - this.logger, - `rulesClient.muteAll('${id}')`, - async () => await this.muteAllWithOCC({ id }) - ); - } - - private async muteAllWithOCC({ id }: { id: string }) { - const { attributes, version } = await this.unsecuredSavedObjectsClient.get( - 'alert', - id - ); - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: WriteOperations.MuteAll, - entity: AlertingAuthorizationEntity.Rule, - }); - - if (attributes.actions.length) { - await this.actionsAuthorization.ensureAuthorized('execute'); - } - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.MUTE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.MUTE, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - - const updateAttributes = this.updateMeta({ - muteAll: true, - mutedInstanceIds: [], - snoozeSchedule: clearUnscheduledSnooze(attributes), - updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), - }); - const updateOptions = { version }; - - await partiallyUpdateAlert( - this.unsecuredSavedObjectsClient, - id, - updateAttributes, - updateOptions - ); - } - - public async unmuteAll({ id }: { id: string }): Promise { - return await retryIfConflicts( - this.logger, - `rulesClient.unmuteAll('${id}')`, - async () => await this.unmuteAllWithOCC({ id }) - ); - } - - private async unmuteAllWithOCC({ id }: { id: string }) { - const { attributes, version } = await this.unsecuredSavedObjectsClient.get( - 'alert', - id - ); - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: WriteOperations.UnmuteAll, - entity: AlertingAuthorizationEntity.Rule, - }); - - if (attributes.actions.length) { - await this.actionsAuthorization.ensureAuthorized('execute'); - } - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.UNMUTE, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.UNMUTE, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - - const updateAttributes = this.updateMeta({ - muteAll: false, - mutedInstanceIds: [], - snoozeSchedule: clearUnscheduledSnooze(attributes), - updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), - }); - const updateOptions = { version }; - - await partiallyUpdateAlert( - this.unsecuredSavedObjectsClient, - id, - updateAttributes, - updateOptions - ); - } - - public async muteInstance({ alertId, alertInstanceId }: MuteOptions): Promise { - return await retryIfConflicts( - this.logger, - `rulesClient.muteInstance('${alertId}')`, - async () => await this.muteInstanceWithOCC({ alertId, alertInstanceId }) - ); - } - - private async muteInstanceWithOCC({ alertId, alertInstanceId }: MuteOptions) { - const { attributes, version } = await this.unsecuredSavedObjectsClient.get( - 'alert', - alertId - ); - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: WriteOperations.MuteAlert, - entity: AlertingAuthorizationEntity.Rule, - }); - - if (attributes.actions.length) { - await this.actionsAuthorization.ensureAuthorized('execute'); - } - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.MUTE_ALERT, - savedObject: { type: 'alert', id: alertId }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.MUTE_ALERT, - outcome: 'unknown', - savedObject: { type: 'alert', id: alertId }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - - const mutedInstanceIds = attributes.mutedInstanceIds || []; - if (!attributes.muteAll && !mutedInstanceIds.includes(alertInstanceId)) { - mutedInstanceIds.push(alertInstanceId); - await this.unsecuredSavedObjectsClient.update( - 'alert', - alertId, - this.updateMeta({ - mutedInstanceIds, - updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), - }), - { version } - ); - } - } - - public async unmuteInstance({ alertId, alertInstanceId }: MuteOptions): Promise { - return await retryIfConflicts( - this.logger, - `rulesClient.unmuteInstance('${alertId}')`, - async () => await this.unmuteInstanceWithOCC({ alertId, alertInstanceId }) - ); - } - - private async unmuteInstanceWithOCC({ - alertId, - alertInstanceId, - }: { - alertId: string; - alertInstanceId: string; - }) { - const { attributes, version } = await this.unsecuredSavedObjectsClient.get( - 'alert', - alertId - ); - - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: WriteOperations.UnmuteAlert, - entity: AlertingAuthorizationEntity.Rule, - }); - if (attributes.actions.length) { - await this.actionsAuthorization.ensureAuthorized('execute'); - } - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.UNMUTE_ALERT, - savedObject: { type: 'alert', id: alertId }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.UNMUTE_ALERT, - outcome: 'unknown', - savedObject: { type: 'alert', id: alertId }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - - const mutedInstanceIds = attributes.mutedInstanceIds || []; - if (!attributes.muteAll && mutedInstanceIds.includes(alertInstanceId)) { - await this.unsecuredSavedObjectsClient.update( - 'alert', - alertId, - this.updateMeta({ - updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), - mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId), - }), - { version } - ); - } - } - - public async runSoon({ id }: { id: string }) { - const { attributes } = await this.unsecuredSavedObjectsClient.get('alert', id); - try { - await this.authorization.ensureAuthorized({ - ruleTypeId: attributes.alertTypeId, - consumer: attributes.consumer, - operation: ReadOperations.RunSoon, - entity: AlertingAuthorizationEntity.Rule, - }); - - if (attributes.actions.length) { - await this.actionsAuthorization.ensureAuthorized('execute'); - } - } catch (error) { - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.RUN_SOON, - savedObject: { type: 'alert', id }, - error, - }) - ); - throw error; - } - - this.auditLogger?.log( - ruleAuditEvent({ - action: RuleAuditAction.RUN_SOON, - outcome: 'unknown', - savedObject: { type: 'alert', id }, - }) - ); - - this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - - // Check that the rule is enabled - if (!attributes.enabled) { - return i18n.translate('xpack.alerting.rulesClient.runSoon.disabledRuleError', { - defaultMessage: 'Error running rule: rule is disabled', - }); - } - - let taskDoc: ConcreteTaskInstance | null = null; - try { - taskDoc = attributes.scheduledTaskId - ? await this.taskManager.get(attributes.scheduledTaskId) - : null; - } catch (err) { - return i18n.translate('xpack.alerting.rulesClient.runSoon.getTaskError', { - defaultMessage: 'Error running rule: {errMessage}', - values: { - errMessage: err.message, - }, - }); - } - - if ( - taskDoc && - (taskDoc.status === TaskStatus.Claiming || taskDoc.status === TaskStatus.Running) - ) { - return i18n.translate('xpack.alerting.rulesClient.runSoon.ruleIsRunning', { - defaultMessage: 'Rule is already running', - }); - } - - try { - await this.taskManager.runSoon(attributes.scheduledTaskId ? attributes.scheduledTaskId : id); - } catch (err) { - return i18n.translate('xpack.alerting.rulesClient.runSoon.runSoonError', { - defaultMessage: 'Error running rule: {errMessage}', - values: { - errMessage: err.message, - }, - }); - } - } - - public async listAlertTypes() { - return await this.authorization.filterByRuleTypeAuthorization( - this.ruleTypeRegistry.list(), - [ReadOperations.Get, WriteOperations.Create], - AlertingAuthorizationEntity.Rule - ); - } + private readonly context: RulesClientContext; + + constructor(context: ConstructorOptions) { + this.context = { + ...context, + minimumScheduleIntervalInMs: parseDuration(context.minimumScheduleInterval.value), + fieldsToExcludeFromPublicApi, + }; + } + + public aggregate = (params?: { options?: AggregateOptions }) => aggregate(this.context, params); + public clone = (...args: CloneArguments) => + clone(this.context, ...args); + public create = (params: CreateOptions) => + create(this.context, params); + public delete = (params: { id: string }) => deleteRule(this.context, params); + public find = (params?: FindParams) => + find(this.context, params); + public get = (params: GetParams) => + get(this.context, params); + public resolve = (params: ResolveParams) => + resolve(this.context, params); + public update = (params: UpdateOptions) => + update(this.context, params); + + public getAlertState = (params: GetAlertStateParams) => getAlertState(this.context, params); + public getAlertSummary = (params: GetAlertSummaryParams) => getAlertSummary(this.context, params); + public getExecutionLogForRule = (params: GetExecutionLogByIdParams) => + getExecutionLogForRule(this.context, params); + public getGlobalExecutionLogWithAuth = (params: GetGlobalExecutionLogParams) => + getGlobalExecutionLogWithAuth(this.context, params); + public getRuleExecutionKPI = (params: GetRuleExecutionKPIParams) => + getRuleExecutionKPI(this.context, params); + public getGlobalExecutionKpiWithAuth = (params: GetGlobalExecutionKPIParams) => + getGlobalExecutionKpiWithAuth(this.context, params); + public getActionErrorLog = (params: GetActionErrorLogByIdParams) => + getActionErrorLog(this.context, params); + public getActionErrorLogWithAuth = (params: GetActionErrorLogByIdParams) => + getActionErrorLogWithAuth(this.context, params); + + public bulkDeleteRules = (options: BulkOptions) => bulkDeleteRules(this.context, options); + public bulkEdit = (options: BulkEditOptions) => + bulkEdit(this.context, options); + public bulkEnableRules = (options: BulkOptions) => bulkEnableRules(this.context, options); + public bulkDisableRules = (options: BulkOptions) => bulkDisableRules(this.context, options); + + public updateApiKey = (options: { id: string }) => updateApiKey(this.context, options); + + public enable = (options: { id: string }) => enable(this.context, options); + public disable = (options: { id: string }) => disable(this.context, options); + + public snooze = (options: SnoozeParams) => snooze(this.context, options); + public unsnooze = (options: UnsnoozeParams) => unsnooze(this.context, options); + + public clearExpiredSnoozes = (options: { id: string }) => + clearExpiredSnoozes(this.context, options); + + public muteAll = (options: { id: string }) => muteAll(this.context, options); + public unmuteAll = (options: { id: string }) => unmuteAll(this.context, options); + public muteInstance = (options: MuteOptions) => muteInstance(this.context, options); + public unmuteInstance = (options: MuteOptions) => unmuteInstance(this.context, options); + + public runSoon = (options: { id: string }) => runSoon(this.context, options); + + public listAlertTypes = () => listAlertTypes(this.context); public getSpaceId(): string | undefined { - return this.spaceId; - } - - private async scheduleTask(opts: ScheduleTaskOptions) { - const { id, consumer, ruleTypeId, schedule, throwOnConflict } = opts; - const taskInstance = { - id, // use the same ID for task document as the rule - taskType: `alerting:${ruleTypeId}`, - schedule, - params: { - alertId: id, - spaceId: this.spaceId, - consumer, - }, - state: { - previousStartedAt: null, - alertTypeState: {}, - alertInstances: {}, - }, - scope: ['alerting'], - enabled: true, - }; - try { - return await this.taskManager.schedule(taskInstance); - } catch (err) { - if (err.statusCode === 409 && !throwOnConflict) { - return taskInstance; - } - throw err; - } - } - - private injectReferencesIntoActions( - alertId: string, - actions: RawRule['actions'], - references: SavedObjectReference[] - ) { - return actions.map((action) => { - if (action.actionRef.startsWith(preconfiguredConnectorActionRefPrefix)) { - return { - ...omit(action, 'actionRef'), - id: action.actionRef.replace(preconfiguredConnectorActionRefPrefix, ''), - }; - } - - const reference = references.find((ref) => ref.name === action.actionRef); - if (!reference) { - throw new Error(`Action reference "${action.actionRef}" not found in alert id: ${alertId}`); - } - return { - ...omit(action, 'actionRef'), - id: reference.id, - }; - }) as Rule['actions']; - } - - private getAlertFromRaw( - id: string, - ruleTypeId: string, - rawRule: RawRule, - references: SavedObjectReference[] | undefined, - includeLegacyId: boolean = false, - excludeFromPublicApi: boolean = false, - includeSnoozeData: boolean = false - ): Rule | RuleWithLegacyId { - const ruleType = this.ruleTypeRegistry.get(ruleTypeId); - // In order to support the partial update API of Saved Objects we have to support - // partial updates of an Alert, but when we receive an actual RawRule, it is safe - // to cast the result to an Alert - const res = this.getPartialRuleFromRaw( - id, - ruleType, - rawRule, - references, - includeLegacyId, - excludeFromPublicApi, - includeSnoozeData - ); - // include to result because it is for internal rules client usage - if (includeLegacyId) { - return res as RuleWithLegacyId; - } - // exclude from result because it is an internal variable - return omit(res, ['legacyId']) as Rule; - } - - private getPartialRuleFromRaw( - id: string, - ruleType: UntypedNormalizedRuleType, - { - createdAt, - updatedAt, - meta, - notifyWhen, - legacyId, - scheduledTaskId, - params, - executionStatus, - monitoring, - nextRun, - schedule, - actions, - snoozeSchedule, - ...partialRawRule - }: Partial, - references: SavedObjectReference[] | undefined, - includeLegacyId: boolean = false, - excludeFromPublicApi: boolean = false, - includeSnoozeData: boolean = false - ): PartialRule | PartialRuleWithLegacyId { - const snoozeScheduleDates = snoozeSchedule?.map((s) => ({ - ...s, - rRule: { - ...s.rRule, - dtstart: new Date(s.rRule.dtstart), - ...(s.rRule.until ? { until: new Date(s.rRule.until) } : {}), - }, - })); - const includeSnoozeSchedule = - snoozeSchedule !== undefined && !isEmpty(snoozeSchedule) && !excludeFromPublicApi; - const isSnoozedUntil = includeSnoozeSchedule - ? this.calculateIsSnoozedUntil({ - muteAll: partialRawRule.muteAll ?? false, - snoozeSchedule, - }) - : null; - const includeMonitoring = monitoring && !excludeFromPublicApi; - const rule = { - id, - notifyWhen, - ...omit(partialRawRule, excludeFromPublicApi ? [...this.fieldsToExcludeFromPublicApi] : ''), - // we currently only support the Interval Schedule type - // Once we support additional types, this type signature will likely change - schedule: schedule as IntervalSchedule, - actions: actions ? this.injectReferencesIntoActions(id, actions, references || []) : [], - params: this.injectReferencesIntoParams(id, ruleType, params, references || []) as Params, - ...(excludeFromPublicApi ? {} : { snoozeSchedule: snoozeScheduleDates ?? [] }), - ...(includeSnoozeData && !excludeFromPublicApi - ? { - activeSnoozes: getActiveScheduledSnoozes({ - snoozeSchedule, - muteAll: partialRawRule.muteAll ?? false, - })?.map((s) => s.id), - isSnoozedUntil, - } - : {}), - ...(updatedAt ? { updatedAt: new Date(updatedAt) } : {}), - ...(createdAt ? { createdAt: new Date(createdAt) } : {}), - ...(scheduledTaskId ? { scheduledTaskId } : {}), - ...(executionStatus - ? { executionStatus: ruleExecutionStatusFromRaw(this.logger, id, executionStatus) } - : {}), - ...(includeMonitoring - ? { monitoring: convertMonitoringFromRawAndVerify(this.logger, id, monitoring) } - : {}), - ...(nextRun ? { nextRun: new Date(nextRun) } : {}), - }; - - return includeLegacyId - ? ({ ...rule, legacyId } as PartialRuleWithLegacyId) - : (rule as PartialRule); - } - - private async validateActions( - alertType: UntypedNormalizedRuleType, - data: Pick & { actions: NormalizedAlertAction[] } - ): Promise { - const { actions, notifyWhen, throttle } = data; - const hasNotifyWhen = typeof notifyWhen !== 'undefined'; - const hasThrottle = typeof throttle !== 'undefined'; - let usesRuleLevelFreqParams; - if (hasNotifyWhen && hasThrottle) usesRuleLevelFreqParams = true; - else if (!hasNotifyWhen && !hasThrottle) usesRuleLevelFreqParams = false; - else { - throw Boom.badRequest( - i18n.translate('xpack.alerting.rulesClient.usesValidGlobalFreqParams.oneUndefined', { - defaultMessage: - 'Rule-level notifyWhen and throttle must both be defined or both be undefined', - }) - ); - } - - if (actions.length === 0) { - return; - } - - // check for actions using connectors with missing secrets - const actionsClient = await this.getActionsClient(); - const actionIds = [...new Set(actions.map((action) => action.id))]; - const actionResults = (await actionsClient.getBulk(actionIds)) || []; - const actionsUsingConnectorsWithMissingSecrets = actionResults.filter( - (result) => result.isMissingSecrets - ); - - if (actionsUsingConnectorsWithMissingSecrets.length) { - throw Boom.badRequest( - i18n.translate('xpack.alerting.rulesClient.validateActions.misconfiguredConnector', { - defaultMessage: 'Invalid connectors: {groups}', - values: { - groups: actionsUsingConnectorsWithMissingSecrets - .map((connector) => connector.name) - .join(', '), - }, - }) - ); - } - - // check for actions with invalid action groups - const { actionGroups: alertTypeActionGroups } = alertType; - const usedAlertActionGroups = actions.map((action) => action.group); - const availableAlertTypeActionGroups = new Set(map(alertTypeActionGroups, 'id')); - const invalidActionGroups = usedAlertActionGroups.filter( - (group) => !availableAlertTypeActionGroups.has(group) - ); - if (invalidActionGroups.length) { - throw Boom.badRequest( - i18n.translate('xpack.alerting.rulesClient.validateActions.invalidGroups', { - defaultMessage: 'Invalid action groups: {groups}', - values: { - groups: invalidActionGroups.join(', '), - }, - }) - ); - } - - // check for actions using frequency params if the rule has rule-level frequency params defined - if (usesRuleLevelFreqParams) { - const actionsWithFrequency = actions.filter((action) => Boolean(action.frequency)); - if (actionsWithFrequency.length) { - throw Boom.badRequest( - i18n.translate('xpack.alerting.rulesClient.validateActions.mixAndMatchFreqParams', { - defaultMessage: - 'Cannot specify per-action frequency params when notify_when and throttle are defined at the rule level: {groups}', - values: { - groups: actionsWithFrequency.map((a) => a.group).join(', '), - }, - }) - ); - } - } else { - const actionsWithoutFrequency = actions.filter((action) => !action.frequency); - if (actionsWithoutFrequency.length) { - throw Boom.badRequest( - i18n.translate('xpack.alerting.rulesClient.validateActions.notAllActionsWithFreq', { - defaultMessage: 'Actions missing frequency parameters: {groups}', - values: { - groups: actionsWithoutFrequency.map((a) => a.group).join(', '), - }, - }) - ); - } - } - } - - private async extractReferences< - Params extends RuleTypeParams, - ExtractedParams extends RuleTypeParams - >( - ruleType: UntypedNormalizedRuleType, - ruleActions: NormalizedAlertAction[], - ruleParams: Params - ): Promise<{ - actions: RawRule['actions']; - params: ExtractedParams; - references: SavedObjectReference[]; - }> { - const { references: actionReferences, actions } = await this.denormalizeActions(ruleActions); - - // Extracts any references using configured reference extractor if available - const extractedRefsAndParams = ruleType?.useSavedObjectReferences?.extractReferences - ? ruleType.useSavedObjectReferences.extractReferences(ruleParams) - : null; - const extractedReferences = extractedRefsAndParams?.references ?? []; - const params = (extractedRefsAndParams?.params as ExtractedParams) ?? ruleParams; - - // Prefix extracted references in order to avoid clashes with framework level references - const paramReferences = extractedReferences.map((reference: SavedObjectReference) => ({ - ...reference, - name: `${extractedSavedObjectParamReferenceNamePrefix}${reference.name}`, - })); - - const references = [...actionReferences, ...paramReferences]; - - return { - actions, - params, - references, - }; - } - - private injectReferencesIntoParams< - Params extends RuleTypeParams, - ExtractedParams extends RuleTypeParams - >( - ruleId: string, - ruleType: UntypedNormalizedRuleType, - ruleParams: SavedObjectAttributes | undefined, - references: SavedObjectReference[] - ): Params { - try { - const paramReferences = references - .filter((reference: SavedObjectReference) => - reference.name.startsWith(extractedSavedObjectParamReferenceNamePrefix) - ) - .map((reference: SavedObjectReference) => ({ - ...reference, - name: reference.name.replace(extractedSavedObjectParamReferenceNamePrefix, ''), - })); - return ruleParams && ruleType?.useSavedObjectReferences?.injectReferences - ? (ruleType.useSavedObjectReferences.injectReferences( - ruleParams as ExtractedParams, - paramReferences - ) as Params) - : (ruleParams as Params); - } catch (err) { - throw Boom.badRequest( - `Error injecting reference into rule params for rule id ${ruleId} - ${err.message}` - ); - } - } - - private async denormalizeActions( - alertActions: NormalizedAlertAction[] - ): Promise<{ actions: RawRule['actions']; references: SavedObjectReference[] }> { - const references: SavedObjectReference[] = []; - const actions: RawRule['actions'] = []; - if (alertActions.length) { - const actionsClient = await this.getActionsClient(); - const actionIds = [...new Set(alertActions.map((alertAction) => alertAction.id))]; - const actionResults = await actionsClient.getBulk(actionIds); - const actionTypeIds = [...new Set(actionResults.map((action) => action.actionTypeId))]; - actionTypeIds.forEach((id) => { - // Notify action type usage via "isActionTypeEnabled" function - actionsClient.isActionTypeEnabled(id, { notifyUsage: true }); - }); - alertActions.forEach(({ id, ...alertAction }, i) => { - const actionResultValue = actionResults.find((action) => action.id === id); - if (actionResultValue) { - if (actionsClient.isPreconfigured(id)) { - actions.push({ - ...alertAction, - actionRef: `${preconfiguredConnectorActionRefPrefix}${id}`, - actionTypeId: actionResultValue.actionTypeId, - }); - } else { - const actionRef = `action_${i}`; - references.push({ - id, - name: actionRef, - type: 'action', - }); - actions.push({ - ...alertAction, - actionRef, - actionTypeId: actionResultValue.actionTypeId, - }); - } - } else { - actions.push({ - ...alertAction, - actionRef: '', - actionTypeId: '', - }); - } - }); - } - return { - actions, - references, - }; - } - - private includeFieldsRequiredForAuthentication(fields: string[]): string[] { - return uniq([...fields, 'alertTypeId', 'consumer']); - } - - private generateAPIKeyName(alertTypeId: string, alertName: string) { - return truncate(`Alerting: ${alertTypeId}/${trim(alertName)}`, { length: 256 }); - } - - private updateMeta>(alertAttributes: T): T { - if (alertAttributes.hasOwnProperty('apiKey') || alertAttributes.hasOwnProperty('apiKeyOwner')) { - alertAttributes.meta = alertAttributes.meta ?? {}; - alertAttributes.meta.versionApiKeyLastmodified = this.kibanaVersion; - } - return alertAttributes; - } -} - -function parseDate(dateString: string | undefined, propertyName: string, defaultValue: Date): Date { - if (dateString === undefined) { - return defaultValue; - } - - const parsedDate = parseIsoOrRelativeDate(dateString); - if (parsedDate === undefined) { - throw Boom.badRequest( - i18n.translate('xpack.alerting.rulesClient.invalidDate', { - defaultMessage: 'Invalid date for parameter {field}: "{dateValue}"', - values: { - field: propertyName, - dateValue: dateString, - }, - }) - ); - } - - return parsedDate; -} - -function getSnoozeAttributes(attributes: RawRule, snoozeSchedule: RuleSnoozeSchedule) { - // If duration is -1, instead mute all - const { id: snoozeId, duration } = snoozeSchedule; - - if (duration === -1) { - return { - muteAll: true, - snoozeSchedule: clearUnscheduledSnooze(attributes), - }; - } - return { - snoozeSchedule: (snoozeId - ? clearScheduledSnoozesById(attributes, [snoozeId]) - : clearUnscheduledSnooze(attributes) - ).concat(snoozeSchedule), - muteAll: false, - }; -} - -function getBulkSnoozeAttributes(attributes: RawRule, snoozeSchedule: RuleSnoozeSchedule) { - // If duration is -1, instead mute all - const { id: snoozeId, duration } = snoozeSchedule; - - if (duration === -1) { - return { - muteAll: true, - snoozeSchedule: clearUnscheduledSnooze(attributes), - }; - } - - // Bulk adding snooze schedule, don't touch the existing snooze/indefinite snooze - if (snoozeId) { - const existingSnoozeSchedules = attributes.snoozeSchedule || []; - return { - muteAll: attributes.muteAll, - snoozeSchedule: [...existingSnoozeSchedules, snoozeSchedule], - }; - } - - // Bulk snoozing, don't touch the existing snooze schedules - return { - muteAll: false, - snoozeSchedule: [...clearUnscheduledSnooze(attributes), snoozeSchedule], - }; -} - -function getUnsnoozeAttributes(attributes: RawRule, scheduleIds?: string[]) { - const snoozeSchedule = scheduleIds - ? clearScheduledSnoozesById(attributes, scheduleIds) - : clearCurrentActiveSnooze(attributes); - - return { - snoozeSchedule, - ...(!scheduleIds ? { muteAll: false } : {}), - }; -} - -function getBulkUnsnoozeAttributes(attributes: RawRule, scheduleIds?: string[]) { - // Bulk removing snooze schedules, don't touch the current snooze/indefinite snooze - if (scheduleIds) { - const newSchedules = clearScheduledSnoozesById(attributes, scheduleIds); - // Unscheduled snooze is also known as snooze now - const unscheduledSnooze = - attributes.snoozeSchedule?.filter((s) => typeof s.id === 'undefined') || []; - - return { - snoozeSchedule: [...unscheduledSnooze, ...newSchedules], - muteAll: attributes.muteAll, - }; - } - - // Bulk unsnoozing, don't touch current snooze schedules that are NOT active - return { - snoozeSchedule: clearCurrentActiveSnooze(attributes), - muteAll: false, - }; -} - -function clearUnscheduledSnooze(attributes: RawRule) { - // Clear any snoozes that have no ID property. These are "simple" snoozes created with the quick UI, e.g. snooze for 3 days starting now - return attributes.snoozeSchedule - ? attributes.snoozeSchedule.filter((s) => typeof s.id !== 'undefined') - : []; -} - -function clearScheduledSnoozesById(attributes: RawRule, ids: string[]) { - return attributes.snoozeSchedule - ? attributes.snoozeSchedule.filter((s) => s.id && !ids.includes(s.id)) - : []; -} - -function clearCurrentActiveSnooze(attributes: RawRule) { - // First attempt to cancel a simple (unscheduled) snooze - const clearedUnscheduledSnoozes = clearUnscheduledSnooze(attributes); - // Now clear any scheduled snoozes that are currently active and never recur - const activeSnoozes = getActiveScheduledSnoozes(attributes); - const activeSnoozeIds = activeSnoozes?.map((s) => s.id) ?? []; - const recurringSnoozesToSkip: string[] = []; - const clearedNonRecurringActiveSnoozes = clearedUnscheduledSnoozes.filter((s) => { - if (!activeSnoozeIds.includes(s.id!)) return true; - // Check if this is a recurring snooze, and return true if so - if (s.rRule.freq && s.rRule.count !== 1) { - recurringSnoozesToSkip.push(s.id!); - return true; - } - }); - const clearedSnoozesAndSkippedRecurringSnoozes = clearedNonRecurringActiveSnoozes.map((s) => { - if (s.id && !recurringSnoozesToSkip.includes(s.id)) return s; - const currentRecurrence = activeSnoozes?.find((a) => a.id === s.id)?.lastOccurrence; - if (!currentRecurrence) return s; - return { - ...s, - skipRecurrences: (s.skipRecurrences ?? []).concat(currentRecurrence.toISOString()), - }; - }); - return clearedSnoozesAndSkippedRecurringSnoozes; -} - -function verifySnoozeScheduleLimit(attributes: Partial) { - const schedules = attributes.snoozeSchedule?.filter((snooze) => snooze.id); - if (schedules && schedules.length > 5) { - throw Error( - i18n.translate('xpack.alerting.rulesClient.snoozeSchedule.limitReached', { - defaultMessage: 'Rule cannot have more than 5 snooze schedules', - }) - ); + return this.context.spaceId; } } diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts index e43ee8a37ae7e..19c020f171f3b 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts @@ -19,6 +19,7 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { loggerMock } from '@kbn/logging-mocks'; +import { enabledRule1, enabledRule2, returnedRule1, returnedRule2 } from './test_helpers'; jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -63,33 +64,9 @@ setGlobalDate(); describe('bulkDelete', () => { let rulesClient: RulesClient; - const existingRule = { - id: 'id1', - type: 'alert', - attributes: {}, - references: [], - version: '123', - }; - const existingDecryptedRule1 = { - ...existingRule, - attributes: { - ...existingRule.attributes, - scheduledTaskId: 'taskId1', - apiKey: Buffer.from('123:abc').toString('base64'), - }, - }; - const existingDecryptedRule2 = { - ...existingRule, - id: 'id2', - attributes: { - ...existingRule.attributes, - scheduledTaskId: 'taskId2', - apiKey: Buffer.from('321:abc').toString('base64'), - }, - }; const mockCreatePointInTimeFinderAsInternalUser = ( - response = { saved_objects: [existingDecryptedRule1, existingDecryptedRule2] } + response = { saved_objects: [enabledRule1, enabledRule2] } ) => { encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest .fn() @@ -103,6 +80,7 @@ describe('bulkDelete', () => { beforeEach(async () => { rulesClient = new RulesClient(rulesClientParams); + mockCreatePointInTimeFinderAsInternalUser(); authorization.getFindAuthorizationFilter.mockResolvedValue({ ensureRuleTypeIsAuthorized() {}, }); @@ -136,7 +114,6 @@ describe('bulkDelete', () => { }); test('should try to delete rules, one successful and one with 500 error', async () => { - mockCreatePointInTimeFinderAsInternalUser(); unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ statuses: [ { id: 'id1', type: 'alert', success: true }, @@ -157,11 +134,11 @@ describe('bulkDelete', () => { expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledTimes(1); expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledWith([ - existingDecryptedRule1, - existingDecryptedRule2, + enabledRule1, + enabledRule2, ]); expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledTimes(1); - expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['taskId1']); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['id1']); expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( { apiKeys: ['MTIzOmFiYw=='] }, @@ -169,7 +146,8 @@ describe('bulkDelete', () => { expect.anything() ); expect(result).toStrictEqual({ - errors: [{ message: 'UPS', rule: { id: 'id2', name: 'n/a' }, status: 500 }], + rules: [returnedRule1], + errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 500 }], total: 2, taskIdsFailedToBeDeleted: [], }); @@ -226,19 +204,19 @@ describe('bulkDelete', () => { .mockResolvedValueOnce({ close: jest.fn(), find: function* asyncGenerator() { - yield { saved_objects: [existingDecryptedRule1, existingDecryptedRule2] }; + yield { saved_objects: [enabledRule1, enabledRule2] }; }, }) .mockResolvedValueOnce({ close: jest.fn(), find: function* asyncGenerator() { - yield { saved_objects: [existingDecryptedRule2] }; + yield { saved_objects: [enabledRule2] }; }, }) .mockResolvedValueOnce({ close: jest.fn(), find: function* asyncGenerator() { - yield { saved_objects: [existingDecryptedRule2] }; + yield { saved_objects: [enabledRule2] }; }, }); @@ -246,7 +224,7 @@ describe('bulkDelete', () => { expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledTimes(3); expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledTimes(1); - expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['taskId1']); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['id1']); expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( { apiKeys: ['MTIzOmFiYw=='] }, @@ -254,7 +232,8 @@ describe('bulkDelete', () => { expect.anything() ); expect(result).toStrictEqual({ - errors: [{ message: 'UPS', rule: { id: 'id2', name: 'n/a' }, status: 409 }], + rules: [returnedRule1], + errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 409 }], total: 2, taskIdsFailedToBeDeleted: [], }); @@ -292,19 +271,19 @@ describe('bulkDelete', () => { .mockResolvedValueOnce({ close: jest.fn(), find: function* asyncGenerator() { - yield { saved_objects: [existingDecryptedRule1, existingDecryptedRule2] }; + yield { saved_objects: [enabledRule1, enabledRule2] }; }, }) .mockResolvedValueOnce({ close: jest.fn(), find: function* asyncGenerator() { - yield { saved_objects: [existingDecryptedRule2] }; + yield { saved_objects: [enabledRule2] }; }, }) .mockResolvedValueOnce({ close: jest.fn(), find: function* asyncGenerator() { - yield { saved_objects: [existingDecryptedRule2] }; + yield { saved_objects: [enabledRule2] }; }, }); @@ -312,7 +291,7 @@ describe('bulkDelete', () => { expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledTimes(2); expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledTimes(1); - expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['taskId1', 'taskId2']); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['id1', 'id2']); expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( { apiKeys: ['MTIzOmFiYw==', 'MzIxOmFiYw=='] }, @@ -320,6 +299,7 @@ describe('bulkDelete', () => { expect.anything() ); expect(result).toStrictEqual({ + rules: [returnedRule1, returnedRule2], errors: [], total: 2, taskIdsFailedToBeDeleted: [], @@ -345,7 +325,6 @@ describe('bulkDelete', () => { }); test('should throw an error if we do not get buckets', async () => { - mockCreatePointInTimeFinderAsInternalUser(); unsecuredSavedObjectsClient.find.mockResolvedValue({ aggregations: { alertTypeId: {}, @@ -363,7 +342,6 @@ describe('bulkDelete', () => { describe('taskManager', () => { test('should return task id if deleting task failed', async () => { - mockCreatePointInTimeFinderAsInternalUser(); unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ statuses: [ { id: 'id1', type: 'alert', success: true }, @@ -373,12 +351,12 @@ describe('bulkDelete', () => { taskManager.bulkRemoveIfExist.mockImplementation(async () => ({ statuses: [ { - id: 'taskId1', + id: 'id1', type: 'alert', success: true, }, { - id: 'taskId2', + id: 'id2', type: 'alert', success: false, error: { @@ -394,16 +372,13 @@ describe('bulkDelete', () => { expect(logger.debug).toBeCalledTimes(1); expect(logger.debug).toBeCalledWith( - 'Successfully deleted schedules for underlying tasks: taskId1' + 'Successfully deleted schedules for underlying tasks: id1' ); expect(logger.error).toBeCalledTimes(1); - expect(logger.error).toBeCalledWith( - 'Failure to delete schedules for underlying tasks: taskId2' - ); + expect(logger.error).toBeCalledWith('Failure to delete schedules for underlying tasks: id2'); }); test('should not throw an error if taskManager throw an error', async () => { - mockCreatePointInTimeFinderAsInternalUser(); unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ statuses: [ { id: 'id1', type: 'alert', success: true }, @@ -418,7 +393,7 @@ describe('bulkDelete', () => { expect(logger.error).toBeCalledTimes(1); expect(logger.error).toBeCalledWith( - 'Failure to delete schedules for underlying tasks: taskId1, taskId2. TaskManager bulkRemoveIfExist failed with Error: UPS' + 'Failure to delete schedules for underlying tasks: id1, id2. TaskManager bulkRemoveIfExist failed with Error: UPS' ); }); @@ -433,12 +408,12 @@ describe('bulkDelete', () => { taskManager.bulkRemoveIfExist.mockImplementation(async () => ({ statuses: [ { - id: 'taskId1', + id: 'id1', type: 'alert', success: true, }, { - id: 'taskId2', + id: 'id2', type: 'alert', success: true, }, @@ -449,7 +424,7 @@ describe('bulkDelete', () => { expect(logger.debug).toBeCalledTimes(1); expect(logger.debug).toBeCalledWith( - 'Successfully deleted schedules for underlying tasks: taskId1, taskId2' + 'Successfully deleted schedules for underlying tasks: id1, id2' ); expect(logger.error).toBeCalledTimes(0); }); @@ -459,7 +434,6 @@ describe('bulkDelete', () => { jest.spyOn(auditLogger, 'log').mockImplementation(); test('logs audit event when deleting rules', async () => { - mockCreatePointInTimeFinderAsInternalUser(); unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ statuses: [ { id: 'id1', type: 'alert', success: true }, @@ -482,7 +456,6 @@ describe('bulkDelete', () => { }); test('logs audit event when authentication failed', async () => { - mockCreatePointInTimeFinderAsInternalUser(); authorization.ensureAuthorized.mockImplementation(() => { throw new Error('Unauthorized'); }); @@ -499,7 +472,6 @@ describe('bulkDelete', () => { }); test('logs audit event when getting an authorization filter failed', async () => { - mockCreatePointInTimeFinderAsInternalUser(); authorization.getFindAuthorizationFilter.mockImplementation(() => { throw new Error('Error'); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts index 7024844c22879..88699a50195e6 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_disable.test.ts @@ -21,16 +21,14 @@ import { loggerMock } from '@kbn/logging-mocks'; import { BulkUpdateTaskResult } from '@kbn/task-manager-plugin/server/task_scheduling'; import { eventLoggerMock } from '@kbn/event-log-plugin/server/mocks'; import { + disabledRule1, disabledRule2, enabledRule1, enabledRule2, savedObjectWith409Error, savedObjectWith500Error, - successfulSavedObject1, - successfulSavedObject2, - successfulSavedObjects, - updatedRule1, - updatedRule2, + returnedDisabledRule1, + returnedDisabledRule2, } from './test_helpers'; jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ @@ -130,7 +128,7 @@ describe('bulkDisableRules', () => { test('should disable two rule', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: successfulSavedObjects, + saved_objects: [disabledRule1, disabledRule2], }); const result = await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); @@ -156,14 +154,14 @@ describe('bulkDisableRules', () => { expect(result).toStrictEqual({ errors: [], - rules: [updatedRule1, updatedRule2], + rules: [returnedDisabledRule1, returnedDisabledRule2], total: 2, }); }); test('should try to disable rules, one successful and one with 500 error', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [successfulSavedObject1, savedObjectWith500Error], + saved_objects: [disabledRule1, savedObjectWith500Error], }); const result = await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); @@ -183,7 +181,7 @@ describe('bulkDisableRules', () => { expect(result).toStrictEqual({ errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 500 }], - rules: [updatedRule1], + rules: [returnedDisabledRule1], total: 2, }); }); @@ -191,7 +189,7 @@ describe('bulkDisableRules', () => { test('should try to disable rules, one successful and one with 409 error, which will not be deleted with retry', async () => { unsecuredSavedObjectsClient.bulkCreate .mockResolvedValueOnce({ - saved_objects: [successfulSavedObject1, savedObjectWith409Error], + saved_objects: [disabledRule1, savedObjectWith409Error], }) .mockResolvedValueOnce({ saved_objects: [savedObjectWith409Error], @@ -228,7 +226,7 @@ describe('bulkDisableRules', () => { expect(taskManager.bulkDisable).toHaveBeenCalledWith(['id1']); expect(result).toStrictEqual({ errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 409 }], - rules: [updatedRule1], + rules: [returnedDisabledRule1], total: 2, }); }); @@ -236,10 +234,10 @@ describe('bulkDisableRules', () => { test('should try to disable rules, one successful and one with 409 error, which successfully will be disabled with retry', async () => { unsecuredSavedObjectsClient.bulkCreate .mockResolvedValueOnce({ - saved_objects: [successfulSavedObject1, savedObjectWith409Error], + saved_objects: [disabledRule1, savedObjectWith409Error], }) .mockResolvedValueOnce({ - saved_objects: [successfulSavedObject2], + saved_objects: [disabledRule2], }); encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest @@ -269,7 +267,7 @@ describe('bulkDisableRules', () => { expect(result).toStrictEqual({ errors: [], - rules: [updatedRule1, updatedRule2], + rules: [returnedDisabledRule1, returnedDisabledRule2], total: 2, }); }); @@ -313,7 +311,7 @@ describe('bulkDisableRules', () => { saved_objects: [enabledRule1, disabledRule2], }); unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [successfulSavedObject1], + saved_objects: [disabledRule1], }); const result = await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); @@ -333,7 +331,7 @@ describe('bulkDisableRules', () => { expect(result).toStrictEqual({ errors: [], - rules: [updatedRule1], + rules: [returnedDisabledRule1], total: 2, }); }); @@ -341,7 +339,7 @@ describe('bulkDisableRules', () => { describe('taskManager', () => { test('should call task manager bulkDisable', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: successfulSavedObjects, + saved_objects: [disabledRule1, disabledRule2], }); taskManager.bulkDisable.mockResolvedValue({ @@ -375,14 +373,16 @@ describe('bulkDisableRules', () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ saved_objects: [ { - ...successfulSavedObject1, + ...enabledRule1, attributes: { + ...enabledRule1.attributes, scheduledTaskId: 'taskId1', }, } as SavedObject, { - ...successfulSavedObject2, + ...enabledRule2, attributes: { + ...enabledRule1, scheduledTaskId: 'taskId2', }, } as SavedObject, @@ -411,7 +411,7 @@ describe('bulkDisableRules', () => { test('should disable one task if one rule was successfully disabled and one has 500 error', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [successfulSavedObject1, savedObjectWith500Error], + saved_objects: [disabledRule1, savedObjectWith500Error], }); taskManager.bulkDisable.mockResolvedValue({ @@ -442,7 +442,7 @@ describe('bulkDisableRules', () => { ], }); unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [successfulSavedObject1], + saved_objects: [disabledRule1], }); await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); @@ -453,7 +453,7 @@ describe('bulkDisableRules', () => { test('should not throw an error if taskManager.bulkDisable throw an error', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: successfulSavedObjects, + saved_objects: [disabledRule1, disabledRule2], }); taskManager.bulkDisable.mockImplementation(() => { throw new Error('Something happend during bulkDisable'); @@ -471,8 +471,9 @@ describe('bulkDisableRules', () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ saved_objects: [ { - ...successfulSavedObject1, + ...disabledRule1, attributes: { + ...disabledRule1.attributes, scheduledTaskId: 'taskId1', }, } as SavedObject, @@ -497,7 +498,7 @@ describe('bulkDisableRules', () => { test('logs audit event when disabling rules', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [successfulSavedObject1], + saved_objects: [disabledRule1], }); await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); @@ -561,7 +562,7 @@ describe('bulkDisableRules', () => { }); test('should call logEvent', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: successfulSavedObjects, + saved_objects: [disabledRule1, disabledRule2], }); await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); @@ -574,7 +575,7 @@ describe('bulkDisableRules', () => { throw new Error('UPS'); }); unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: successfulSavedObjects, + saved_objects: [disabledRule1, disabledRule2], }); await rulesClient.bulkDisableRules({ filter: 'fake_filter' }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts index 2d2901c6a06c2..a48db94bd13e4 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts @@ -21,14 +21,14 @@ import { BulkUpdateTaskResult } from '@kbn/task-manager-plugin/server/task_sched import { disabledRule1, disabledRule2, + disabledRuleWithAction1, + disabledRuleWithAction2, + enabledRule1, enabledRule2, savedObjectWith409Error, savedObjectWith500Error, - successfulSavedObject1, - successfulSavedObject2, - successfulSavedObjects, - updatedRule1, - updatedRule2, + returnedRule1, + returnedRule2, } from './test_helpers'; jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ @@ -126,7 +126,7 @@ describe('bulkEnableRules', () => { test('should enable two rule', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: successfulSavedObjects, + saved_objects: [enabledRule1, enabledRule2], }); const result = await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); @@ -152,7 +152,7 @@ describe('bulkEnableRules', () => { expect(result).toStrictEqual({ errors: [], - rules: [updatedRule1, updatedRule2], + rules: [returnedRule1, returnedRule2], total: 2, taskIdsFailedToBeEnabled: [], }); @@ -160,7 +160,7 @@ describe('bulkEnableRules', () => { test('should try to enable rules, one successful and one with 500 error', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [successfulSavedObject1, savedObjectWith500Error], + saved_objects: [enabledRule1, savedObjectWith500Error], }); const result = await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); @@ -180,7 +180,7 @@ describe('bulkEnableRules', () => { expect(result).toStrictEqual({ errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 500 }], - rules: [updatedRule1], + rules: [returnedRule1], total: 2, taskIdsFailedToBeEnabled: [], }); @@ -189,7 +189,7 @@ describe('bulkEnableRules', () => { test('should try to enable rules, one successful and one with 409 error, which will not be deleted with retry', async () => { unsecuredSavedObjectsClient.bulkCreate .mockResolvedValueOnce({ - saved_objects: [successfulSavedObject1, savedObjectWith409Error], + saved_objects: [enabledRule1, savedObjectWith409Error], }) .mockResolvedValueOnce({ saved_objects: [savedObjectWith409Error], @@ -224,7 +224,7 @@ describe('bulkEnableRules', () => { expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(3); expect(result).toStrictEqual({ errors: [{ message: 'UPS', rule: { id: 'id2', name: 'fakeName' }, status: 409 }], - rules: [updatedRule1], + rules: [returnedRule1], total: 2, taskIdsFailedToBeEnabled: [], }); @@ -233,10 +233,10 @@ describe('bulkEnableRules', () => { test('should try to enable rules, one successful and one with 409 error, which successfully will be deleted with retry', async () => { unsecuredSavedObjectsClient.bulkCreate .mockResolvedValueOnce({ - saved_objects: [successfulSavedObject1, savedObjectWith409Error], + saved_objects: [enabledRule1, savedObjectWith409Error], }) .mockResolvedValueOnce({ - saved_objects: [successfulSavedObject2], + saved_objects: [enabledRule2], }); encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest @@ -265,13 +265,13 @@ describe('bulkEnableRules', () => { expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledTimes(2); expect(result).toStrictEqual({ errors: [], - rules: [updatedRule1, updatedRule2], + rules: [returnedRule1, returnedRule2], total: 2, taskIdsFailedToBeEnabled: [], }); }); - test('should thow an error if number of matched rules greater than 10,000', async () => { + test('should throw an error if number of matched rules greater than 10,000', async () => { unsecuredSavedObjectsClient.find.mockResolvedValue({ aggregations: { alertTypeId: { @@ -305,7 +305,10 @@ describe('bulkEnableRules', () => { ); }); - test('should thow if there are actions, but do not have execute permissions', async () => { + test('should throw if there are actions, but do not have execute permissions', async () => { + mockCreatePointInTimeFinderAsInternalUser({ + saved_objects: [disabledRuleWithAction1, disabledRuleWithAction2], + }); actionsAuthorization.ensureAuthorized.mockImplementation(() => { throw new Error('UPS'); }); @@ -339,7 +342,7 @@ describe('bulkEnableRules', () => { saved_objects: [disabledRule1, enabledRule2], }); unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [successfulSavedObject1], + saved_objects: [enabledRule1], }); const result = await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); @@ -359,7 +362,7 @@ describe('bulkEnableRules', () => { expect(result).toStrictEqual({ errors: [], - rules: [updatedRule1], + rules: [returnedRule1], total: 2, taskIdsFailedToBeEnabled: [], }); @@ -368,7 +371,7 @@ describe('bulkEnableRules', () => { describe('taskManager', () => { test('should return task id if deleting task failed', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: successfulSavedObjects, + saved_objects: [enabledRule1, enabledRule2], }); taskManager.bulkEnable.mockImplementation( async () => @@ -400,7 +403,7 @@ describe('bulkEnableRules', () => { expect(result).toStrictEqual({ errors: [], - rules: [updatedRule1, updatedRule2], + rules: [returnedRule1, returnedRule2], total: 2, taskIdsFailedToBeEnabled: ['id2'], }); @@ -408,7 +411,7 @@ describe('bulkEnableRules', () => { test('should not throw an error if taskManager throw an error', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: successfulSavedObjects, + saved_objects: [enabledRule1, enabledRule2], }); taskManager.bulkEnable.mockImplementation(() => { throw new Error('UPS'); @@ -423,7 +426,7 @@ describe('bulkEnableRules', () => { expect(result).toStrictEqual({ errors: [], - rules: [updatedRule1, updatedRule2], + rules: [returnedRule1, returnedRule2], taskIdsFailedToBeEnabled: ['id1', 'id2'], total: 2, }); @@ -431,7 +434,7 @@ describe('bulkEnableRules', () => { test('should call task manager bulkEnable for two tasks', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: successfulSavedObjects, + saved_objects: [enabledRule1, enabledRule2], }); await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); @@ -442,7 +445,7 @@ describe('bulkEnableRules', () => { test('should should call task manager bulkEnable only for one task, if one rule have an error', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [successfulSavedObject1, savedObjectWith500Error], + saved_objects: [enabledRule1, savedObjectWith500Error], }); await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); @@ -456,7 +459,7 @@ describe('bulkEnableRules', () => { saved_objects: [disabledRule1, enabledRule2], }); unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [successfulSavedObject1], + saved_objects: [enabledRule1], }); taskManager.bulkEnable.mockImplementation( @@ -485,7 +488,7 @@ describe('bulkEnableRules', () => { test('logs audit event when enabling rules', async () => { unsecuredSavedObjectsClient.bulkCreate.mockResolvedValue({ - saved_objects: [successfulSavedObject1], + saved_objects: [enabledRule1], }); await rulesClient.bulkEnableRules({ filter: 'fake_filter' }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index 4d9b84e53a2f4..6205b4bb4ba6a 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -6,7 +6,8 @@ */ import { schema } from '@kbn/config-schema'; -import { RulesClient, ConstructorOptions, CreateOptions } from '../rules_client'; +import { CreateOptions } from '../methods/create'; +import { RulesClient, ConstructorOptions } from '../rules_client'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index 4cfa13f69b84d..1b65d27c8e72e 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -54,7 +54,7 @@ beforeEach(() => { setGlobalDate(); -jest.mock('../lib/map_sort_field', () => ({ +jest.mock('../common/map_sort_field', () => ({ mapSortField: jest.fn(), })); @@ -288,7 +288,7 @@ describe('find()', () => { test('calls mapSortField', async () => { const rulesClient = new RulesClient(rulesClientParams); await rulesClient.find({ options: { sortField: 'name' } }); - expect(jest.requireMock('../lib/map_sort_field').mapSortField).toHaveBeenCalledWith('name'); + expect(jest.requireMock('../common/map_sort_field').mapSortField).toHaveBeenCalledWith('name'); }); test('should translate filter/sort/search on params to mapped_params', async () => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts index 6b635abe5d7f0..672281469d18b 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { RulesClient, ConstructorOptions, GetActionErrorLogByIdParams } from '../rules_client'; +import { RulesClient, ConstructorOptions } from '../rules_client'; +import { GetActionErrorLogByIdParams } from '../methods/get_action_error_log'; import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { fromKueryExpression } from '@kbn/es-query'; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/test_helpers.ts b/x-pack/plugins/alerting/server/rules_client/tests/test_helpers.ts index 10c93aa1e88b4..0100242672be9 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/test_helpers.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/test_helpers.ts @@ -7,24 +7,6 @@ import type { SavedObject } from '@kbn/core-saved-objects-common'; -export const successfulSavedObject1 = { - id: 'id1', - version: '1', - attributes: { - scheduledTaskId: 'id1', - }, -} as SavedObject; - -export const successfulSavedObject2 = { - id: 'id2', - version: '1', - attributes: { - scheduledTaskId: 'id2', - }, -} as SavedObject; - -export const successfulSavedObjects = [successfulSavedObject1, successfulSavedObject2]; - export const savedObjectWith500Error = { id: 'id2', error: { @@ -53,16 +35,7 @@ export const defaultRule = { consumer: 'fakeConsumer', alertTypeId: 'fakeType', schedule: { interval: '5m' }, - actions: [ - { - group: 'default', - actionTypeId: '1', - actionRef: '1', - params: { - foo: true, - }, - }, - ], + actions: [] as unknown, }, references: [], version: '1', @@ -85,7 +58,7 @@ export const enabledRule2 = { ...defaultRule.attributes, enabled: true, scheduledTaskId: 'id2', - apiKey: Buffer.from('123:abc').toString('base64'), + apiKey: Buffer.from('321:abc').toString('base64'), }, }; @@ -94,7 +67,7 @@ export const disabledRule1 = { attributes: { ...defaultRule.attributes, enabled: false, - scheduledTaskId: 'id2', + scheduledTaskId: 'id1', apiKey: Buffer.from('123:abc').toString('base64'), }, }; @@ -110,33 +83,80 @@ export const disabledRule2 = { }, }; -export const rule2 = { - ...defaultRule, - id: 'id2', +export const disabledRuleWithAction1 = { + ...disabledRule1, attributes: { - ...defaultRule.attributes, - enabled: true, - scheduledTaskId: 'id2', - apiKey: Buffer.from('321:abc').toString('base64'), + ...disabledRule1.attributes, + actions: [ + { + group: 'default', + actionTypeId: '1', + actionRef: '1', + params: { + foo: true, + }, + }, + ], + }, +}; + +export const disabledRuleWithAction2 = { + ...disabledRule2, + attributes: { + ...disabledRule2.attributes, + actions: [ + { + group: 'default', + actionTypeId: '1', + actionRef: '1', + params: { + foo: true, + }, + }, + ], }, }; -export const updatedRule1 = { +export const returnedRule1 = { actions: [], + alertTypeId: 'fakeType', + apiKey: 'MTIzOmFiYw==', + consumer: 'fakeConsumer', + enabled: true, id: 'id1', + name: 'fakeName', notifyWhen: undefined, params: undefined, - schedule: undefined, - snoozeSchedule: [], + schedule: { + interval: '5m', + }, scheduledTaskId: 'id1', + snoozeSchedule: [], }; -export const updatedRule2 = { +export const returnedRule2 = { actions: [], + alertTypeId: 'fakeType', + apiKey: 'MzIxOmFiYw==', + consumer: 'fakeConsumer', + enabled: true, id: 'id2', + name: 'fakeName', notifyWhen: undefined, params: undefined, - schedule: undefined, - snoozeSchedule: [], + schedule: { + interval: '5m', + }, scheduledTaskId: 'id2', + snoozeSchedule: [], +}; + +export const returnedDisabledRule1 = { + ...returnedRule1, + enabled: false, +}; + +export const returnedDisabledRule2 = { + ...returnedRule2, + enabled: false, }; diff --git a/x-pack/plugins/alerting/server/rules_client/types.ts b/x-pack/plugins/alerting/server/rules_client/types.ts new file mode 100644 index 0000000000000..ff59b5527f902 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/types.ts @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KueryNode } from '@kbn/es-query'; +import { Logger, SavedObjectsClientContract, PluginInitializerContext } from '@kbn/core/server'; +import { ActionsClient, ActionsAuthorization } from '@kbn/actions-plugin/server'; +import { + GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult, + InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, +} from '@kbn/security-plugin/server'; +import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; +import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; +import { IEventLogClient, IEventLogger } from '@kbn/event-log-plugin/server'; +import { AuditLogger } from '@kbn/security-plugin/server'; +import { RegistryRuleType } from '../rule_type_registry'; +import { + RuleTypeRegistry, + RuleAction, + IntervalSchedule, + SanitizedRule, + RuleSnoozeSchedule, +} from '../types'; +import { AlertingAuthorization } from '../authorization'; +import { AlertingRulesConfig } from '../config'; + +export type { + BulkEditOperation, + BulkEditFields, + BulkEditOptions, + BulkEditOptionsFilter, + BulkEditOptionsIds, +} from './methods/bulk_edit'; +export type { CreateOptions } from './methods/create'; +export type { FindOptions, FindResult } from './methods/find'; +export type { UpdateOptions } from './methods/update'; +export type { AggregateOptions, AggregateResult } from './methods/aggregate'; +export type { GetAlertSummaryParams } from './methods/get_alert_summary'; +export type { + GetExecutionLogByIdParams, + GetGlobalExecutionLogParams, +} from './methods/get_execution_log'; +export type { + GetGlobalExecutionKPIParams, + GetRuleExecutionKPIParams, +} from './methods/get_execution_kpi'; +export type { GetActionErrorLogByIdParams } from './methods/get_action_error_log'; + +export interface RulesClientContext { + readonly logger: Logger; + readonly getUserName: () => Promise; + readonly spaceId: string; + readonly namespace?: string; + readonly taskManager: TaskManagerStartContract; + readonly unsecuredSavedObjectsClient: SavedObjectsClientContract; + readonly authorization: AlertingAuthorization; + readonly ruleTypeRegistry: RuleTypeRegistry; + readonly minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval']; + readonly minimumScheduleIntervalInMs: number; + readonly createAPIKey: (name: string) => Promise; + readonly getActionsClient: () => Promise; + readonly actionsAuthorization: ActionsAuthorization; + readonly getEventLogClient: () => Promise; + readonly encryptedSavedObjectsClient: EncryptedSavedObjectsClient; + readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; + readonly auditLogger?: AuditLogger; + readonly eventLogger?: IEventLogger; + readonly fieldsToExcludeFromPublicApi: Array; +} + +export type NormalizedAlertAction = Omit; + +export interface RegistryAlertTypeWithAuth extends RegistryRuleType { + authorizedConsumers: string[]; +} +export type CreateAPIKeyResult = + | { apiKeysEnabled: false } + | { apiKeysEnabled: true; result: SecurityPluginGrantAPIKeyResult }; +export type InvalidateAPIKeyResult = + | { apiKeysEnabled: false } + | { apiKeysEnabled: true; result: SecurityPluginInvalidateAPIKeyResult }; + +export interface RuleBulkOperationAggregation { + alertTypeId: { + buckets: Array<{ + key: string[]; + doc_count: number; + }>; + }; +} +export interface SavedObjectOptions { + id?: string; + migrationVersion?: Record; +} + +export interface ScheduleTaskOptions { + id: string; + consumer: string; + ruleTypeId: string; + schedule: IntervalSchedule; + throwOnConflict: boolean; // whether to throw conflict errors or swallow them +} + +export interface IndexType { + [key: string]: unknown; +} + +export interface MuteOptions extends IndexType { + alertId: string; + alertInstanceId: string; +} + +export interface SnoozeOptions extends IndexType { + snoozeSchedule: RuleSnoozeSchedule; +} + +export interface BulkOptionsFilter { + filter?: string | KueryNode; +} + +export interface BulkOptionsIds { + ids?: string[]; +} + +export type BulkOptions = BulkOptionsFilter | BulkOptionsIds; + +export interface BulkOperationError { + message: string; + status?: number; + rule: { + id: string; + name: string; + }; +} + +export type BulkAction = 'DELETE' | 'ENABLE' | 'DISABLE'; + +export interface RuleBulkOperationAggregation { + alertTypeId: { + buckets: Array<{ + key: string[]; + doc_count: number; + }>; + }; +} diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations/8.2/index.ts b/x-pack/plugins/alerting/server/saved_objects/migrations/8.2/index.ts index 6de67875ba2eb..9a8967c9556ab 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations/8.2/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations/8.2/index.ts @@ -7,7 +7,7 @@ import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; -import { getMappedParams } from '../../../rules_client/lib/mapped_params_utils'; +import { getMappedParams } from '../../../rules_client/common'; import { RawRule } from '../../../types'; import { createEsoMigration, pipeMigrations } from '../utils'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js index 86731ca161177..6258eaec4a754 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/essql.js @@ -7,7 +7,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { EsqlLang } from '@kbn/monaco'; +import { SQLLang } from '@kbn/monaco'; import { EuiFormRow, EuiLink, EuiText } from '@elastic/eui'; import { CodeEditorField } from '@kbn/kibana-react-plugin/public'; import { getSimpleArg, setSimpleArg } from '../../../public/lib/arg_helpers'; @@ -78,7 +78,7 @@ class EssqlDatasource extends PureComponent { } > { [caseData.comments] ); - const { isLoading: isLoadingAlertFeatureIds, alertFeatureIds } = + const { isLoading: isLoadingAlertFeatureIds, data: alertFeatureIds } = useGetFeatureIds(alertRegistrationContexts); const alertStateProps = { alertsTableConfigurationRegistry: triggersActionsUi.alertsTableConfigurationRegistry, configurationId: caseData.owner, id: `case-details-alerts-${caseData.owner}`, - flyoutSize: (alertFeatureIds.includes('siem') ? 'm' : 's') as EuiFlyoutSize, - featureIds: alertFeatureIds, + flyoutSize: (alertFeatureIds?.includes('siem') ? 'm' : 's') as EuiFlyoutSize, + featureIds: alertFeatureIds ?? [], query: alertIdsQuery, - showExpandToDetails: alertFeatureIds.includes('siem'), + showExpandToDetails: Boolean(alertFeatureIds?.includes('siem')), }; if (alertIdsQuery.ids.values.length === 0) { diff --git a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx index 06f958f6149cc..2a073a1c42afa 100644 --- a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx @@ -5,11 +5,13 @@ * 2.0. */ +import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; import { basicCase } from '../../containers/mock'; import { useUpdateComment } from '../../containers/use_update_comment'; import { useRefreshCaseViewPage } from '../case_view/use_on_refresh_case_view_page'; +import { TestProviders } from '../../common/mock'; import { useLensDraftComment } from '../markdown_editor/plugins/lens/use_lens_draft_comment'; import { NEW_COMMENT_ID } from './constants'; import { useUserActionsHandler } from './use_user_actions_handler'; @@ -26,6 +28,8 @@ const patchComment = jest.fn(); const clearDraftComment = jest.fn(); const openLensModal = jest.fn(); +const wrapper: React.FC = ({ children }) => {children}; + describe('useUserActionsHandler', () => { beforeAll(() => { jest.useFakeTimers({ legacyFakeTimers: true }); @@ -52,7 +56,9 @@ describe('useUserActionsHandler', () => { }); it('should save a comment', async () => { - const { result } = renderHook(() => useUserActionsHandler()); + const { result } = renderHook(() => useUserActionsHandler(), { + wrapper, + }); result.current.handleSaveComment({ id: 'test-id', version: 'test-version' }, 'a comment'); expect(patchComment).toHaveBeenCalledWith({ @@ -64,14 +70,18 @@ describe('useUserActionsHandler', () => { }); it('should refresh the case case after updating', async () => { - const { result } = renderHook(() => useUserActionsHandler()); + const { result } = renderHook(() => useUserActionsHandler(), { + wrapper, + }); result.current.handleUpdate(basicCase); expect(useRefreshCaseViewPage()).toHaveBeenCalled(); }); it('should handle markdown edit', async () => { - const { result } = renderHook(() => useUserActionsHandler()); + const { result } = renderHook(() => useUserActionsHandler(), { + wrapper, + }); act(() => { result.current.handleManageMarkdownEditId('test-id'); @@ -82,7 +92,9 @@ describe('useUserActionsHandler', () => { }); it('should remove id from the markdown edit ids', async () => { - const { result } = renderHook(() => useUserActionsHandler()); + const { result } = renderHook(() => useUserActionsHandler(), { + wrapper, + }); act(() => { result.current.handleManageMarkdownEditId('test-id'); @@ -98,7 +110,9 @@ describe('useUserActionsHandler', () => { }); it('should outline a comment', async () => { - const { result } = renderHook(() => useUserActionsHandler()); + const { result } = renderHook(() => useUserActionsHandler(), { + wrapper, + }); act(() => { result.current.handleOutlineComment('test-id'); @@ -115,7 +129,9 @@ describe('useUserActionsHandler', () => { it('should quote', async () => { const addQuote = jest.fn(); - const { result } = renderHook(() => useUserActionsHandler()); + const { result } = renderHook(() => useUserActionsHandler(), { + wrapper, + }); result.current.commentRefs.current[NEW_COMMENT_ID] = { addQuote, diff --git a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.tsx b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.tsx index 2b80426e26e93..f6bf5372eedc0 100644 --- a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.tsx @@ -43,7 +43,7 @@ export const useUserActionsHandler = (): UseUserActionsHandler => { useLensDraftComment(); const handlerTimeoutId = useRef(0); const { isLoadingIds, patchComment } = useUpdateComment(); - const { deleteComment } = useDeleteComment(); + const { mutate: deleteComment } = useDeleteComment(); const [selectedOutlineCommentId, setSelectedOutlineCommentId] = useState(''); const [manageMarkdownEditIds, setManageMarkdownEditIds] = useState([]); const refreshCaseViewPage = useRefreshCaseViewPage(); diff --git a/x-pack/plugins/cases/public/containers/constants.ts b/x-pack/plugins/cases/public/containers/constants.ts index b9e7dea71c3f6..2e57a17be08ec 100644 --- a/x-pack/plugins/cases/public/containers/constants.ts +++ b/x-pack/plugins/cases/public/containers/constants.ts @@ -14,6 +14,7 @@ export const casesQueriesKeys = { all: ['cases'] as const, users: ['users'] as const, connectors: ['connectors'] as const, + alerts: ['alerts'] as const, connectorsList: () => [...casesQueriesKeys.connectors, 'list'] as const, casesList: () => [...casesQueriesKeys.all, 'list'] as const, casesMetrics: () => [...casesQueriesKeys.casesList(), 'metrics'] as const, @@ -32,9 +33,12 @@ export const casesQueriesKeys = { connectorTypes: () => [...casesQueriesKeys.connectors, 'types'] as const, license: () => [...casesQueriesKeys.connectors, 'license'] as const, tags: () => [...casesQueriesKeys.all, 'tags'] as const, + alertFeatureIds: (alertRegistrationContexts: string[]) => + [...casesQueriesKeys.alerts, 'features', alertRegistrationContexts] as const, }; export const casesMutationsKeys = { deleteCases: ['delete-cases'] as const, updateCases: ['update-cases'] as const, + deleteComment: ['delete-comment'] as const, }; diff --git a/x-pack/plugins/cases/public/containers/use_delete_comment.test.tsx b/x-pack/plugins/cases/public/containers/use_delete_comment.test.tsx index dd280e8abb0bf..2fe3a2f21eaa1 100644 --- a/x-pack/plugins/cases/public/containers/use_delete_comment.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_comment.test.tsx @@ -5,12 +5,15 @@ * 2.0. */ +import React from 'react'; import { act, renderHook } from '@testing-library/react-hooks'; import type { UseDeleteComment } from './use_delete_comment'; import { useDeleteComment } from './use_delete_comment'; import * as api from './api'; import { basicCaseId } from './mock'; +import { TestProviders } from '../common/mock'; import { useRefreshCaseViewPage } from '../components/case_view/use_on_refresh_case_view_page'; +import { useToasts } from '../common/lib/kibana'; jest.mock('../common/lib/kibana'); jest.mock('./api'); @@ -18,81 +21,95 @@ jest.mock('../components/case_view/use_on_refresh_case_view_page'); const commentId = 'ab124'; +const wrapper: React.FC = ({ children }) => {children}; + describe('useDeleteComment', () => { + const addSuccess = jest.fn(); + const addError = jest.fn(); + + (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('init', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDeleteComment() - ); - await waitForNextUpdate(); - expect(result.current).toEqual({ - isError: false, - deleteComment: result.current.deleteComment, - }); + const { result } = renderHook(() => useDeleteComment(), { + wrapper, }); + + expect(result.current).toBeTruthy(); }); it('calls deleteComment with correct arguments - case', async () => { const spyOnDeleteComment = jest.spyOn(api, 'deleteComment'); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDeleteComment() - ); - await waitForNextUpdate(); + const { waitForNextUpdate, result } = renderHook( + () => useDeleteComment(), + { + wrapper, + } + ); - result.current.deleteComment({ + act(() => { + result.current.mutate({ caseId: basicCaseId, commentId, }); - await waitForNextUpdate(); - expect(spyOnDeleteComment).toBeCalledWith({ - caseId: basicCaseId, - commentId, - signal: expect.any(AbortSignal), - }); - expect(result.current.isError).toBe(false); + }); + + await waitForNextUpdate(); + + expect(spyOnDeleteComment).toBeCalledWith({ + caseId: basicCaseId, + commentId, + signal: expect.any(AbortSignal), }); }); it('refreshes the case page view after delete', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDeleteComment() - ); - await waitForNextUpdate(); + const { waitForNextUpdate, result } = renderHook( + () => useDeleteComment(), + { + wrapper, + } + ); - result.current.deleteComment({ - caseId: basicCaseId, - commentId, - }); - await waitForNextUpdate(); - expect(useRefreshCaseViewPage()).toBeCalled(); + result.current.mutate({ + caseId: basicCaseId, + commentId, }); + + await waitForNextUpdate(); + + expect(useRefreshCaseViewPage()).toBeCalled(); }); it('sets isError when fails to delete a case', async () => { const spyOnDeleteComment = jest.spyOn(api, 'deleteComment'); spyOnDeleteComment.mockRejectedValue(new Error('Not possible :O')); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useDeleteComment() - ); - await waitForNextUpdate(); + const { waitForNextUpdate, result } = renderHook( + () => useDeleteComment(), + { + wrapper, + } + ); - result.current.deleteComment({ - caseId: basicCaseId, - commentId, - }); - await waitForNextUpdate(); - expect(spyOnDeleteComment).toBeCalledWith({ - caseId: basicCaseId, - commentId, - signal: expect.any(AbortSignal), - }); + result.current.mutate({ + caseId: basicCaseId, + commentId, + }); + + await waitForNextUpdate(); - expect(result.current.isError).toBe(true); + expect(spyOnDeleteComment).toBeCalledWith({ + caseId: basicCaseId, + commentId, + signal: expect.any(AbortSignal), }); + + expect(addError).toHaveBeenCalled(); + expect(result.current.isError).toBe(true); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_delete_comment.tsx b/x-pack/plugins/cases/public/containers/use_delete_comment.tsx index 8c0d9d204d6cc..58de861baf383 100644 --- a/x-pack/plugins/cases/public/containers/use_delete_comment.tsx +++ b/x-pack/plugins/cases/public/containers/use_delete_comment.tsx @@ -5,106 +5,38 @@ * 2.0. */ -import { useReducer, useCallback, useRef, useEffect } from 'react'; -import { useToasts } from '../common/lib/kibana'; +import { useMutation } from '@tanstack/react-query'; +import { casesMutationsKeys } from './constants'; +import type { ServerError } from '../types'; import { useRefreshCaseViewPage } from '../components/case_view/use_on_refresh_case_view_page'; +import { useCasesToast } from '../common/use_cases_toast'; import { deleteComment } from './api'; import * as i18n from './translations'; -interface CommentDeleteState { - isError: boolean; -} -interface CommentDelete { - commentId: string; -} - -type Action = - | { type: 'FETCH_INIT'; payload: string } - | { type: 'FETCH_SUCCESS'; payload: CommentDelete } - | { type: 'FETCH_FAILURE'; payload: string }; - -const dataFetchReducer = (state: CommentDeleteState, action: Action): CommentDeleteState => { - switch (action.type) { - case 'FETCH_INIT': - return { - ...state, - isError: false, - }; - - case 'FETCH_SUCCESS': - return { - ...state, - isError: false, - }; - case 'FETCH_FAILURE': - return { - ...state, - isError: true, - }; - default: - return state; - } -}; - -interface DeleteComment { +interface MutationArgs { caseId: string; commentId: string; } -export interface UseDeleteComment extends CommentDeleteState { - deleteComment: ({ caseId, commentId }: DeleteComment) => void; -} - -export const useDeleteComment = (): UseDeleteComment => { - const [state, dispatch] = useReducer(dataFetchReducer, { - isError: false, - }); - const toasts = useToasts(); - const isCancelledRef = useRef(false); - const abortCtrlRef = useRef(new AbortController()); +export const useDeleteComment = () => { + const { showErrorToast } = useCasesToast(); const refreshCaseViewPage = useRefreshCaseViewPage(); - const dispatchDeleteComment = useCallback( - async ({ caseId, commentId }: DeleteComment) => { - try { - isCancelledRef.current = false; - abortCtrlRef.current.abort(); - abortCtrlRef.current = new AbortController(); - dispatch({ type: 'FETCH_INIT', payload: commentId }); - - await deleteComment({ - caseId, - commentId, - signal: abortCtrlRef.current.signal, - }); - - if (!isCancelledRef.current) { - refreshCaseViewPage(); - dispatch({ type: 'FETCH_SUCCESS', payload: { commentId } }); - } - } catch (error) { - if (!isCancelledRef.current) { - if (error.name !== 'AbortError') { - toasts.addError( - error.body && error.body.message ? new Error(error.body.message) : error, - { title: i18n.ERROR_TITLE } - ); - } - dispatch({ type: 'FETCH_FAILURE', payload: commentId }); - } - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); - - useEffect( - () => () => { - isCancelledRef.current = true; - abortCtrlRef.current.abort(); + return useMutation( + ({ caseId, commentId }: MutationArgs) => { + const abortCtrlRef = new AbortController(); + return deleteComment({ caseId, commentId, signal: abortCtrlRef.signal }); }, - [] + { + mutationKey: casesMutationsKeys.deleteComment, + onSuccess: () => { + refreshCaseViewPage(); + }, + onError: (error: ServerError) => { + showErrorToast(error, { title: i18n.ERROR_TITLE }); + }, + } ); - - return { ...state, deleteComment: dispatchDeleteComment }; }; + +export type UseDeleteComment = ReturnType; diff --git a/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx b/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx index 3d4382bc709ab..9ac53b3726d38 100644 --- a/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_feature_ids.test.tsx @@ -5,9 +5,11 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; -import React from 'react'; -import { TestProviders } from '../common/mock'; +import { renderHook } from '@testing-library/react-hooks'; +import { waitFor } from '@testing-library/dom'; +import { useToasts } from '../common/lib/kibana'; +import type { AppMockRenderer } from '../common/mock'; +import { createAppMockRenderer } from '../common/mock'; import { useGetFeatureIds } from './use_get_feature_ids'; import * as api from './api'; @@ -15,56 +17,54 @@ jest.mock('./api'); jest.mock('../common/lib/kibana'); describe('useGetFeaturesIds', () => { + const addSuccess = jest.fn(); + const addError = jest.fn(); + + (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); + + let appMockRender: AppMockRenderer; + beforeEach(() => { + appMockRender = createAppMockRenderer(); jest.clearAllMocks(); - jest.restoreAllMocks(); }); - it('inits with empty data', async () => { - jest.spyOn(api, 'getFeatureIds').mockRejectedValue([]); - const { result } = renderHook(() => useGetFeatureIds(['context1']), { - wrapper: ({ children }) => {children}, - }); + it('returns the features ids correctly', async () => { + const spy = jest.spyOn(api, 'getFeatureIds').mockRejectedValue([]); - await act(async () => { - expect(result.current.alertFeatureIds).toEqual([]); - expect(result.current.isLoading).toEqual(true); - expect(result.current.isError).toEqual(false); + const { waitForNextUpdate } = renderHook(() => useGetFeatureIds(['context1']), { + wrapper: appMockRender.AppWrapper, }); - }); - it('fetches data and returns it correctly', async () => { - const spy = jest.spyOn(api, 'getFeatureIds'); - const { result } = renderHook(() => useGetFeatureIds(['context1']), { - wrapper: ({ children }) => {children}, - }); + await waitForNextUpdate(); - await act(async () => { + await waitFor(() => { expect(spy).toHaveBeenCalledWith( { registrationContext: ['context1'] }, expect.any(AbortSignal) ); }); - - await act(async () => { - expect(result.current.alertFeatureIds).toEqual(['siem', 'observability']); - expect(result.current.isLoading).toEqual(false); - expect(result.current.isError).toEqual(false); - }); }); - it('sets isError to true when an error occurs', async () => { - const spy = jest.spyOn(api, 'getFeatureIds'); - spy.mockImplementation(() => { - throw new Error('Something went wrong'); - }); + it('shows a toast error when the api return an error', async () => { + (useToasts as jest.Mock).mockReturnValue({ addError }); + + const spy = jest + .spyOn(api, 'getFeatureIds') + .mockRejectedValue(new Error('Something went wrong')); - const { result } = renderHook(() => useGetFeatureIds(['context1']), { - wrapper: ({ children }) => {children}, + const { waitForNextUpdate } = renderHook(() => useGetFeatureIds(['context1']), { + wrapper: appMockRender.AppWrapper, }); - expect(result.current.alertFeatureIds).toEqual([]); - expect(result.current.isLoading).toEqual(false); - expect(result.current.isError).toEqual(true); + await waitForNextUpdate(); + + await waitFor(() => { + expect(spy).toHaveBeenCalledWith( + { registrationContext: ['context1'] }, + expect.any(AbortSignal) + ); + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/containers/use_get_feature_ids.tsx b/x-pack/plugins/cases/public/containers/use_get_feature_ids.tsx index 082e0539792ff..e5da391950c35 100644 --- a/x-pack/plugins/cases/public/containers/use_get_feature_ids.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_feature_ids.tsx @@ -5,70 +5,30 @@ * 2.0. */ -import { useCallback, useEffect, useState, useRef } from 'react'; +import { useQuery } from '@tanstack/react-query'; import type { ValidFeatureId } from '@kbn/rule-data-utils'; - +import type { ServerError } from '../types'; +import { useCasesToast } from '../common/use_cases_toast'; import * as i18n from './translations'; -import { useToasts } from '../common/lib/kibana'; import { getFeatureIds } from './api'; +import { casesQueriesKeys } from './constants'; -const initialStatus = { - isLoading: true, - alertFeatureIds: [] as ValidFeatureId[], - isError: false, -}; - -export const useGetFeatureIds = ( - alertRegistrationContexts: string[] -): { - isLoading: boolean; - isError: boolean; - alertFeatureIds: ValidFeatureId[]; -} => { - const toasts = useToasts(); - const isCancelledRef = useRef(false); - const abortCtrlRef = useRef(new AbortController()); - const [status, setStatus] = useState(initialStatus); - - const fetchFeatureIds = useCallback( - async (registrationContext: string[]) => { - setStatus({ isLoading: true, alertFeatureIds: [], isError: false }); - try { - isCancelledRef.current = false; - abortCtrlRef.current.abort(); - abortCtrlRef.current = new AbortController(); +export const useGetFeatureIds = (alertRegistrationContexts: string[]) => { + const { showErrorToast } = useCasesToast(); - const query = { registrationContext }; - const response = await getFeatureIds(query, abortCtrlRef.current.signal); - - if (!isCancelledRef.current) { - setStatus({ isLoading: false, alertFeatureIds: response, isError: false }); - } - } catch (error) { - if (!isCancelledRef.current) { - setStatus({ isLoading: false, alertFeatureIds: [], isError: true }); - if (error.name !== 'AbortError') { - toasts.addError( - error.body && error.body.message ? new Error(error.body.message) : error, - { title: i18n.ERROR_TITLE } - ); - } - } - } + return useQuery( + casesQueriesKeys.alertFeatureIds(alertRegistrationContexts), + () => { + const abortCtrlRef = new AbortController(); + const query = { registrationContext: alertRegistrationContexts }; + return getFeatureIds(query, abortCtrlRef.signal); }, - [toasts] + { + onError: (error: ServerError) => { + showErrorToast(error, { title: i18n.ERROR_TITLE }); + }, + } ); - - useEffect(() => { - fetchFeatureIds(alertRegistrationContexts); - return () => { - isCancelledRef.current = true; - abortCtrlRef.current.abort(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, alertRegistrationContexts); - - return status; }; export type UseGetFeatureIds = typeof useGetFeatureIds; diff --git a/x-pack/plugins/cases/server/client/attachments/delete.ts b/x-pack/plugins/cases/server/client/attachments/delete.ts index 3a1ad6a358b69..28fcaa9219243 100644 --- a/x-pack/plugins/cases/server/client/attachments/delete.ts +++ b/x-pack/plugins/cases/server/client/attachments/delete.ts @@ -87,7 +87,6 @@ export async function deleteAll( }); await userActionService.bulkCreateAttachmentDeletion({ - unsecuredSavedObjectsClient, caseId: caseID, attachments: comments.saved_objects.map((comment) => ({ id: comment.id, @@ -154,7 +153,6 @@ export async function deleteComment( await userActionService.createUserAction({ type: ActionTypes.comment, action: Actions.delete, - unsecuredSavedObjectsClient, caseId: id, attachmentId: attachmentID, payload: { attachment: { ...myComment.attributes } }, diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts index 7a249400ccf8d..0da0f42b93f2f 100644 --- a/x-pack/plugins/cases/server/client/cases/create.ts +++ b/x-pack/plugins/cases/server/client/cases/create.ts @@ -40,7 +40,6 @@ export const create = async ( clientArgs: CasesClientArgs ): Promise => { const { - unsecuredSavedObjectsClient, services: { caseService, userActionService, licensingService, notificationService }, user, logger, @@ -105,7 +104,6 @@ export const create = async ( await userActionService.createUserAction({ type: ActionTypes.create_case, - unsecuredSavedObjectsClient, caseId: newCase.id, user, payload: { diff --git a/x-pack/plugins/cases/server/client/cases/delete.ts b/x-pack/plugins/cases/server/client/cases/delete.ts index 7f09bb46288c2..0b7a6a6839e5c 100644 --- a/x-pack/plugins/cases/server/client/cases/delete.ts +++ b/x-pack/plugins/cases/server/client/cases/delete.ts @@ -5,11 +5,13 @@ * 2.0. */ -import pMap from 'p-map'; import { Boom } from '@hapi/boom'; -import type { SavedObjectsFindResponse } from '@kbn/core/server'; -import type { CommentAttributes } from '../../../common/api'; -import { MAX_CONCURRENT_SEARCHES } from '../../../common/constants'; +import type { SavedObjectsBulkDeleteObject } from '@kbn/core/server'; +import { + CASE_COMMENT_SAVED_OBJECT, + CASE_SAVED_OBJECT, + CASE_USER_ACTION_SAVED_OBJECT, +} from '../../../common/constants'; import type { CasesClientArgs } from '..'; import { createCaseError } from '../../common/error'; import type { OwnerEntity } from '../../authorization'; @@ -23,7 +25,6 @@ import { Operations } from '../../authorization'; export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): Promise { const { unsecuredSavedObjectsClient, - user, services: { caseService, attachmentService, userActionService }, logger, authorization, @@ -49,56 +50,27 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P entities: Array.from(entities.values()), }); - const deleteCasesMapper = async (id: string) => - caseService.deleteCase({ - id, - refresh: false, - }); - - // Ensuring we don't too many concurrent deletions running. - await pMap(ids, deleteCasesMapper, { - concurrency: MAX_CONCURRENT_SEARCHES, + const attachmentIds = await attachmentService.getAttachmentIdsForCases({ + caseIds: ids, + unsecuredSavedObjectsClient, }); - const getCommentsMapper = async (id: string) => - caseService.getAllCaseComments({ - id, - }); - - // Ensuring we don't too many concurrent get running. - const comments = await pMap(ids, getCommentsMapper, { - concurrency: MAX_CONCURRENT_SEARCHES, - }); + const userActionIds = await userActionService.getUserActionIdsForCases(ids); - /** - * This is a nested pMap.Mapper. - * Each element of the comments array contains all comments of a particular case. - * For that reason we need first to create a map that iterate over all cases - * and return a pMap that deletes the comments for that case - */ - const deleteCommentsMapper = async (commentRes: SavedObjectsFindResponse) => - pMap(commentRes.saved_objects, (comment) => - attachmentService.delete({ - unsecuredSavedObjectsClient, - attachmentId: comment.id, - refresh: false, - }) - ); + const bulkDeleteEntities: SavedObjectsBulkDeleteObject[] = [ + ...ids.map((id) => ({ id, type: CASE_SAVED_OBJECT })), + ...attachmentIds.map((id) => ({ id, type: CASE_COMMENT_SAVED_OBJECT })), + ...userActionIds.map((id) => ({ id, type: CASE_USER_ACTION_SAVED_OBJECT })), + ]; - // Ensuring we don't too many concurrent deletions running. - await pMap(comments, deleteCommentsMapper, { - concurrency: MAX_CONCURRENT_SEARCHES, + await caseService.bulkDeleteCaseEntities({ + entities: bulkDeleteEntities, + options: { refresh: 'wait_for' }, }); - await userActionService.bulkCreateCaseDeletion({ - unsecuredSavedObjectsClient, - cases: cases.saved_objects.map((caseInfo) => ({ - id: caseInfo.id, - owner: caseInfo.attributes.owner, - connectorId: caseInfo.attributes.connector.id, - })), - user, - }); + await userActionService.bulkAuditLogCaseDeletion( + cases.saved_objects.map((caseInfo) => caseInfo.id) + ); } catch (error) { throw createCaseError({ message: `Failed to delete cases ids: ${JSON.stringify(ids)}: ${error}`, diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index 2e020677faa6f..9f5e9adbb4815 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -259,7 +259,6 @@ export const push = async ( if (shouldMarkAsClosed) { await userActionService.createUserAction({ type: ActionTypes.status, - unsecuredSavedObjectsClient, payload: { status: CaseStatuses.closed }, user, caseId, @@ -274,7 +273,6 @@ export const push = async ( await userActionService.createUserAction({ type: ActionTypes.pushed, - unsecuredSavedObjectsClient, payload: { externalService }, user, caseId, diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index 3d6c595a55e03..6a304bf12e3e6 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -305,7 +305,6 @@ export const update = async ( clientArgs: CasesClientArgs ): Promise => { const { - unsecuredSavedObjectsClient, services: { caseService, userActionService, @@ -446,7 +445,6 @@ export const update = async ( }, [] as CaseResponse[]); await userActionService.bulkCreateUpdateCase({ - unsecuredSavedObjectsClient, originalCases: myCases.saved_objects, updatedCases: updatedCases.saved_objects, user, diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts index b054903c8a2d0..9816f8444e399 100644 --- a/x-pack/plugins/cases/server/client/factory.ts +++ b/x-pack/plugins/cases/server/client/factory.ts @@ -14,7 +14,11 @@ import type { IBasePath, } from '@kbn/core/server'; import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; -import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; +import type { + AuditLogger, + SecurityPluginSetup, + SecurityPluginStart, +} from '@kbn/security-plugin/server'; import type { PluginStartContract as FeaturesPluginStart } from '@kbn/features-plugin/server'; import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; import type { LensServerPluginSetup } from '@kbn/lens-plugin/server'; @@ -119,6 +123,7 @@ export class CasesClientFactory { unsecuredSavedObjectsClient, esClient: scopedClusterClient, request, + auditLogger, }); const userInfo = await this.getUserInfo(request); @@ -149,10 +154,12 @@ export class CasesClientFactory { unsecuredSavedObjectsClient, esClient, request, + auditLogger, }: { unsecuredSavedObjectsClient: SavedObjectsClientContract; esClient: ElasticsearchClient; request: KibanaRequest; + auditLogger: AuditLogger; }): CasesServices { this.validateInitialization(); @@ -190,10 +197,12 @@ export class CasesClientFactory { caseService, caseConfigureService: new CaseConfigureService(this.logger), connectorMappingsService: new ConnectorMappingsService(this.logger), - userActionService: new CaseUserActionService( - this.logger, - this.options.persistableStateAttachmentTypeRegistry - ), + userActionService: new CaseUserActionService({ + log: this.logger, + persistableStateAttachmentTypeRegistry: this.options.persistableStateAttachmentTypeRegistry, + unsecuredSavedObjectsClient, + auditLogger, + }), attachmentService, licensingService, notificationService, diff --git a/x-pack/plugins/cases/server/client/metrics/connectors.ts b/x-pack/plugins/cases/server/client/metrics/connectors.ts index e5ab7c2856048..5248efb30939e 100644 --- a/x-pack/plugins/cases/server/client/metrics/connectors.ts +++ b/x-pack/plugins/cases/server/client/metrics/connectors.ts @@ -18,7 +18,6 @@ export class Connectors extends SingleCaseBaseHandler { public async compute(): Promise { const { - unsecuredSavedObjectsClient, authorization, services: { userActionService }, logger, @@ -29,7 +28,6 @@ export class Connectors extends SingleCaseBaseHandler { ); const uniqueConnectors = await userActionService.getUniqueConnectors({ - unsecuredSavedObjectsClient, caseId: this.caseId, filter: authorizationFilter, }); diff --git a/x-pack/plugins/cases/server/client/metrics/lifespan.ts b/x-pack/plugins/cases/server/client/metrics/lifespan.ts index e040228622b0b..83e1b32d1bbc3 100644 --- a/x-pack/plugins/cases/server/client/metrics/lifespan.ts +++ b/x-pack/plugins/cases/server/client/metrics/lifespan.ts @@ -26,7 +26,6 @@ export class Lifespan extends SingleCaseBaseHandler { public async compute(): Promise { const { - unsecuredSavedObjectsClient, authorization, services: { userActionService }, logger, @@ -49,7 +48,6 @@ export class Lifespan extends SingleCaseBaseHandler { ); const statusUserActions = await userActionService.findStatusChanges({ - unsecuredSavedObjectsClient, caseId: this.caseId, filter: authorizationFilter, }); diff --git a/x-pack/plugins/cases/server/client/user_actions/get.ts b/x-pack/plugins/cases/server/client/user_actions/get.ts index 835029315e434..3b1addfe7c6c5 100644 --- a/x-pack/plugins/cases/server/client/user_actions/get.ts +++ b/x-pack/plugins/cases/server/client/user_actions/get.ts @@ -18,17 +18,13 @@ export const get = async ( clientArgs: CasesClientArgs ): Promise => { const { - unsecuredSavedObjectsClient, services: { userActionService }, logger, authorization, } = clientArgs; try { - const userActions = await userActionService.getAll({ - unsecuredSavedObjectsClient, - caseId, - }); + const userActions = await userActionService.getAll(caseId); await authorization.ensureAuthorized({ entities: userActions.saved_objects.map((userAction) => ({ diff --git a/x-pack/plugins/cases/server/common/models/case_with_comments.ts b/x-pack/plugins/cases/server/common/models/case_with_comments.ts index 5bff0757af718..1cc40b48d51e6 100644 --- a/x-pack/plugins/cases/server/common/models/case_with_comments.ts +++ b/x-pack/plugins/cases/server/common/models/case_with_comments.ts @@ -185,7 +185,6 @@ export class CaseCommentModel { await this.params.services.userActionService.createUserAction({ type: ActionTypes.comment, action: Actions.update, - unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient, caseId: this.caseInfo.id, attachmentId: comment.id, payload: { attachment: queryRestAttributes }, @@ -339,7 +338,6 @@ export class CaseCommentModel { await this.params.services.userActionService.createUserAction({ type: ActionTypes.comment, action: Actions.create, - unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient, caseId: this.caseInfo.id, attachmentId: comment.id, payload: { @@ -352,7 +350,6 @@ export class CaseCommentModel { private async bulkCreateCommentUserAction(attachments: Array<{ id: string } & CommentRequest>) { await this.params.services.userActionService.bulkCreateAttachmentCreation({ - unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient, caseId: this.caseInfo.id, attachments: attachments.map(({ id, ...attachment }) => ({ id, diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts index 325038b016c45..bda8ab9333b1c 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.ts @@ -100,6 +100,43 @@ export class AttachmentService { private readonly persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry ) {} + public async getAttachmentIdsForCases({ + caseIds, + unsecuredSavedObjectsClient, + }: { + caseIds: string[]; + unsecuredSavedObjectsClient: SavedObjectsClientContract; + }) { + try { + this.log.debug(`Attempting to retrieve attachments associated with cases: [${caseIds}]`); + + const finder = unsecuredSavedObjectsClient.createPointInTimeFinder({ + type: CASE_COMMENT_SAVED_OBJECT, + hasReference: caseIds.map((id) => ({ id, type: CASE_SAVED_OBJECT })), + sortField: 'created_at', + sortOrder: 'asc', + /** + * We only care about the ids so to reduce the data returned we should limit the fields in the response. Core + * doesn't support retrieving no fields (id would always be returned anyway) so to limit it we'll only request + * the owner even though we don't need it. + */ + fields: ['owner'], + perPage: MAX_DOCS_PER_PAGE, + }); + + const ids: string[] = []; + + for await (const attachmentSavedObject of finder.find()) { + ids.push(...attachmentSavedObject.saved_objects.map((attachment) => attachment.id)); + } + + return ids; + } catch (error) { + this.log.error(`Error retrieving attachments associated with cases: [${caseIds}]: ${error}`); + throw error; + } + } + public async countAlertsAttachedToCase( params: AlertsAttachedToCaseArgs ): Promise { diff --git a/x-pack/plugins/cases/server/services/cases/index.ts b/x-pack/plugins/cases/server/services/cases/index.ts index 1ab431f85ba3c..86d89ff4ef869 100644 --- a/x-pack/plugins/cases/server/services/cases/index.ts +++ b/x-pack/plugins/cases/server/services/cases/index.ts @@ -15,6 +15,8 @@ import type { SavedObjectsUpdateResponse, SavedObjectsResolveResponse, SavedObjectsFindOptions, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, } from '@kbn/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -308,6 +310,21 @@ export class CasesService { } } + public async bulkDeleteCaseEntities({ + entities, + options, + }: { + entities: SavedObjectsBulkDeleteObject[]; + options?: SavedObjectsBulkDeleteOptions; + }) { + try { + this.log.debug(`Attempting to bulk delete case entities ${JSON.stringify(entities)}`); + return await this.unsecuredSavedObjectsClient.bulkDelete(entities, options); + } catch (error) { + this.log.error(`Error bulk deleting case entities ${JSON.stringify(entities)}: ${error}`); + } + } + public async getCase({ id: caseId }: GetCaseArgs): Promise { try { this.log.debug(`Attempting to GET case ${caseId}`); diff --git a/x-pack/plugins/cases/server/services/mocks.ts b/x-pack/plugins/cases/server/services/mocks.ts index 46b9cd12830c5..125fbc7ec9650 100644 --- a/x-pack/plugins/cases/server/services/mocks.ts +++ b/x-pack/plugins/cases/server/services/mocks.ts @@ -75,16 +75,15 @@ export const connectorMappingsServiceMock = (): ConnectorMappingsServiceMock => export const createUserActionServiceMock = (): CaseUserActionServiceMock => { const service: PublicMethodsOf = { - bulkCreateCaseDeletion: jest.fn(), + bulkAuditLogCaseDeletion: jest.fn(), bulkCreateUpdateCase: jest.fn(), bulkCreateAttachmentDeletion: jest.fn(), bulkCreateAttachmentCreation: jest.fn(), createUserAction: jest.fn(), - create: jest.fn(), getAll: jest.fn(), - bulkCreate: jest.fn(), findStatusChanges: jest.fn(), getUniqueConnectors: jest.fn(), + getUserActionIdsForCases: jest.fn(), }; // the cast here is required because jest.Mocked tries to include private members and would throw an error @@ -117,6 +116,7 @@ export const createAttachmentServiceMock = (): AttachmentServiceMock => { getCaseCommentStats: jest.fn(), valueCountAlertsAttachedToCase: jest.fn(), executeCaseAggregations: jest.fn(), + getAttachmentIdsForCases: jest.fn(), }; // the cast here is required because jest.Mocked tries to include private members and would throw an error diff --git a/x-pack/plugins/cases/server/services/user_actions/__snapshots__/audit_logger.test.ts.snap b/x-pack/plugins/cases/server/services/user_actions/__snapshots__/audit_logger.test.ts.snap new file mode 100644 index 0000000000000..b419945a73616 --- /dev/null +++ b/x-pack/plugins/cases/server/services/user_actions/__snapshots__/audit_logger.test.ts.snap @@ -0,0 +1,116 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`UserActionAuditLogger logs add user action as event.type change 1`] = ` +Array [ + Object { + "event": Object { + "action": "action", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "123", + "type": "type", + }, + }, + "message": "id: idParam", + }, +] +`; + +exports[`UserActionAuditLogger logs create user action as event.type creation 1`] = ` +Array [ + Object { + "event": Object { + "action": "action", + "category": Array [ + "database", + ], + "type": Array [ + "creation", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "123", + "type": "type", + }, + }, + "message": "id: idParam", + }, +] +`; + +exports[`UserActionAuditLogger logs delete user action as event.type deletion 1`] = ` +Array [ + Object { + "event": Object { + "action": "action", + "category": Array [ + "database", + ], + "type": Array [ + "deletion", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "123", + "type": "type", + }, + }, + "message": "id: idParam", + }, +] +`; + +exports[`UserActionAuditLogger logs push_to_service user action as event.type creation 1`] = ` +Array [ + Object { + "event": Object { + "action": "action", + "category": Array [ + "database", + ], + "type": Array [ + "creation", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "123", + "type": "type", + }, + }, + "message": "id: idParam", + }, +] +`; + +exports[`UserActionAuditLogger logs update user action as event.type change 1`] = ` +Array [ + Object { + "event": Object { + "action": "action", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "123", + "type": "type", + }, + }, + "message": "id: idParam", + }, +] +`; diff --git a/x-pack/plugins/cases/server/services/user_actions/__snapshots__/index.test.ts.snap b/x-pack/plugins/cases/server/services/user_actions/__snapshots__/index.test.ts.snap new file mode 100644 index 0000000000000..6a576b04bc547 --- /dev/null +++ b/x-pack/plugins/cases/server/services/user_actions/__snapshots__/index.test.ts.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CaseUserActionService methods createUserAction create case comment logs a comment user action of action: create 1`] = ` +Array [ + Object { + "event": Object { + "action": "case_user_action_create_comment", + "category": Array [ + "database", + ], + "type": Array [ + "creation", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "test-id", + "type": "cases-comments", + }, + }, + "message": "User created comment id: test-id for case id: 123 - user action id: created_user_action_id", + }, +] +`; + +exports[`CaseUserActionService methods createUserAction create case comment logs a comment user action of action: delete 1`] = ` +Array [ + Object { + "event": Object { + "action": "case_user_action_delete_comment", + "category": Array [ + "database", + ], + "type": Array [ + "deletion", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "test-id", + "type": "cases-comments", + }, + }, + "message": "User deleted comment id: test-id for case id: 123 - user action id: created_user_action_id", + }, +] +`; + +exports[`CaseUserActionService methods createUserAction create case comment logs a comment user action of action: update 1`] = ` +Array [ + Object { + "event": Object { + "action": "case_user_action_update_comment", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "test-id", + "type": "cases-comments", + }, + }, + "message": "User changed comment id: test-id for case id: 123 - user action id: created_user_action_id", + }, +] +`; diff --git a/x-pack/plugins/cases/server/services/user_actions/abstract_builder.ts b/x-pack/plugins/cases/server/services/user_actions/abstract_builder.ts index 30f66bd9ece67..bbade0dbb74cd 100644 --- a/x-pack/plugins/cases/server/services/user_actions/abstract_builder.ts +++ b/x-pack/plugins/cases/server/services/user_actions/abstract_builder.ts @@ -19,9 +19,10 @@ import { ActionTypes, NONE_CONNECTOR_ID } from '../../../common/api'; import type { BuilderDeps, BuilderParameters, - BuilderReturnValue, CommonBuilderArguments, + SavedObjectParameters, UserActionParameters, + UserActionEvent, } from './types'; import type { PersistableStateAttachmentTypeRegistry } from '../../attachment_framework/persistable_state_registry'; @@ -98,7 +99,7 @@ export abstract class UserActionBuilder { attachmentId, connectorId, type, - }: CommonBuilderArguments): BuilderReturnValue => { + }: CommonBuilderArguments): SavedObjectParameters => { return { attributes: { ...this.getCommonUserActionAttributes({ user, owner }), @@ -121,5 +122,5 @@ export abstract class UserActionBuilder { public abstract build( args: UserActionParameters - ): BuilderReturnValue; + ): UserActionEvent; } diff --git a/x-pack/plugins/cases/server/services/user_actions/audit_logger.test.ts b/x-pack/plugins/cases/server/services/user_actions/audit_logger.test.ts new file mode 100644 index 0000000000000..6c8d02a80c20f --- /dev/null +++ b/x-pack/plugins/cases/server/services/user_actions/audit_logger.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Actions } from '../../../common/api'; +import type { AuditLogger } from '@kbn/security-plugin/server'; +import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; +import { UserActionAuditLogger } from './audit_logger'; +import type { EventDetails } from './types'; + +describe('UserActionAuditLogger', () => { + let mockLogger: jest.Mocked; + + beforeEach(() => { + mockLogger = auditLoggerMock.create(); + }); + + it.each([ + [Actions.add, 'change'], + [Actions.create, 'creation'], + [Actions.delete, 'deletion'], + [Actions.push_to_service, 'creation'], + [Actions.update, 'change'], + ])('logs %s user action as event.type %s', (action, type) => { + const eventDetails: EventDetails = { + getMessage: (id?: string) => `id: ${id}`, + action, + descriptiveAction: 'action', + savedObjectId: '123', + savedObjectType: 'type', + }; + + const logger = new UserActionAuditLogger(mockLogger); + logger.log(eventDetails, 'idParam'); + + expect(mockLogger.log.mock.calls[0]).toMatchSnapshot(); + }); + + it('does not call the internal audit logger when the event details are undefined', () => { + const logger = new UserActionAuditLogger(mockLogger); + + logger.log(); + + expect(mockLogger.log).not.toBeCalled(); + }); +}); diff --git a/x-pack/plugins/cases/server/services/user_actions/audit_logger.ts b/x-pack/plugins/cases/server/services/user_actions/audit_logger.ts new file mode 100644 index 0000000000000..adbe29901b9fd --- /dev/null +++ b/x-pack/plugins/cases/server/services/user_actions/audit_logger.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EcsEventType } from '@kbn/logging'; +import type { AuditLogger } from '@kbn/security-plugin/server'; +import type { UserAction as Action } from '../../../common/api'; +import type { EventDetails } from './types'; + +const actionsToEcsType: Record = { + add: 'change', + delete: 'deletion', + create: 'creation', + push_to_service: 'creation', + update: 'change', +}; + +export class UserActionAuditLogger { + constructor(private readonly auditLogger: AuditLogger) {} + + public log(event?: EventDetails, storedUserActionId?: string) { + if (!event) { + return; + } + + this.auditLogger.log({ + message: event.getMessage(storedUserActionId), + event: { + action: event.descriptiveAction, + category: ['database'], + type: [actionsToEcsType[event.action]], + }, + kibana: { + saved_object: { + type: event.savedObjectType, + id: event.savedObjectId, + }, + }, + }); + } +} diff --git a/x-pack/plugins/cases/server/services/user_actions/builder_factory.test.ts b/x-pack/plugins/cases/server/services/user_actions/builder_factory.test.ts index 5ffd8a2e670a2..b1a0bb24007f0 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builder_factory.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builder_factory.test.ts @@ -25,12 +25,19 @@ import { casePayload, externalService } from './mocks'; describe('UserActionBuilder', () => { const persistableStateAttachmentTypeRegistry = createPersistableStateAttachmentTypeRegistryMock(); - const builderFactory = new BuilderFactory({ persistableStateAttachmentTypeRegistry }); const commonArgs = { caseId: '123', user: { full_name: 'Elastic User', username: 'elastic', email: 'elastic@elastic.co' }, owner: SECURITY_SOLUTION_OWNER, }; + let builderFactory: BuilderFactory; + + beforeEach(() => { + jest.clearAllMocks(); + builderFactory = new BuilderFactory({ + persistableStateAttachmentTypeRegistry, + }); + }); beforeAll(() => { jest.useFakeTimers(); @@ -41,690 +48,1134 @@ describe('UserActionBuilder', () => { jest.useRealTimers(); }); - it('builds a title user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.title)!; - const userAction = builder.build({ - payload: { title: 'test' }, - ...commonArgs, - }); + describe('parameters', () => { + it('builds a title user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.title)!; + const userAction = builder.build({ + payload: { title: 'test' }, + ...commonArgs, + }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "action": "update", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "title": "test", + }, + "type": "title", }, - "owner": "securitySolution", - "payload": Object { - "title": "test", + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + ], + } + `); + }); + + it('builds a connector user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.connector)!; + const userAction = builder.build({ + payload: { + connector: { + id: '456', + name: 'ServiceNow SN', + type: ConnectorTypes.serviceNowSIR, + fields: { + category: 'Denial of Service', + destIp: true, + malwareHash: true, + malwareUrl: true, + priority: '2', + sourceIp: true, + subcategory: '45', + }, }, - "type": "title", }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - ], - } - `); - }); + ...commonArgs, + }); - it('builds a connector user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.connector)!; - const userAction = builder.build({ - payload: { - connector: { - id: '456', - name: 'ServiceNow SN', - type: ConnectorTypes.serviceNowSIR, - fields: { - category: 'Denial of Service', - destIp: true, - malwareHash: true, - malwareUrl: true, - priority: '2', - sourceIp: true, - subcategory: '45', + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "connector": Object { + "fields": Object { + "category": "Denial of Service", + "destIp": true, + "malwareHash": true, + "malwareUrl": true, + "priority": "2", + "sourceIp": true, + "subcategory": "45", + }, + "name": "ServiceNow SN", + "type": ".servicenow-sir", + }, + }, + "type": "connector", }, - }, - }, - ...commonArgs, + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "456", + "name": "connectorId", + "type": "action", + }, + ], + } + `); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "action": "update", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", + it('builds a comment user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.comment)!; + const userAction = builder.build({ + action: Actions.update, + payload: { + attachment: { + comment: 'a comment!', + type: CommentType.user, + owner: SECURITY_SOLUTION_OWNER, }, - "owner": "securitySolution", - "payload": Object { - "connector": Object { - "fields": Object { - "category": "Denial of Service", - "destIp": true, - "malwareHash": true, - "malwareUrl": true, - "priority": "2", - "sourceIp": true, - "subcategory": "45", + }, + attachmentId: 'test-id', + ...commonArgs, + }); + + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "comment": Object { + "comment": "a comment!", + "owner": "securitySolution", + "type": "user", }, - "name": "ServiceNow SN", - "type": ".servicenow-sir", }, + "type": "comment", }, - "type": "connector", + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "test-id", + "name": "associated-cases-comments", + "type": "cases-comments", + }, + ], + } + `); + }); + + it('builds an external reference attachment (savedObject) user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.comment)!; + const userAction = builder.build({ + action: Actions.update, + payload: { + attachment: externalReferenceAttachmentSO, }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - Object { - "id": "456", - "name": "connectorId", - "type": "action", + attachmentId: 'test-id', + ...commonArgs, + }); + + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "comment": Object { + "externalReferenceAttachmentTypeId": ".test", + "externalReferenceMetadata": null, + "externalReferenceStorage": Object { + "soType": "test-so", + "type": "savedObject", + }, + "owner": "securitySolution", + "type": "externalReference", + }, + }, + "type": "comment", }, - ], - } - `); - }); + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "test-id", + "name": "associated-cases-comments", + "type": "cases-comments", + }, + Object { + "id": "my-id", + "name": "externalReferenceId", + "type": "test-so", + }, + ], + } + `); + }); - it('builds a comment user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.comment)!; - const userAction = builder.build({ - action: Actions.update, - payload: { - attachment: { - comment: 'a comment!', - type: CommentType.user, - owner: SECURITY_SOLUTION_OWNER, + it('builds an external reference attachment (elasticSearchDoc) user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.comment)!; + const userAction = builder.build({ + action: Actions.update, + payload: { + attachment: externalReferenceAttachmentES, }, - }, - attachmentId: 'test-id', - ...commonArgs, - }); + attachmentId: 'test-id', + ...commonArgs, + }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "action": "update", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object { - "comment": Object { - "comment": "a comment!", - "owner": "securitySolution", - "type": "user", + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", }, + "owner": "securitySolution", + "payload": Object { + "comment": Object { + "externalReferenceAttachmentTypeId": ".test", + "externalReferenceId": "my-id", + "externalReferenceMetadata": null, + "externalReferenceStorage": Object { + "type": "elasticSearchDoc", + }, + "owner": "securitySolution", + "type": "externalReference", + }, + }, + "type": "comment", }, - "type": "comment", + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "test-id", + "name": "associated-cases-comments", + "type": "cases-comments", + }, + ], + } + `); + }); + + it('builds a persistable state attachment user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.comment)!; + const userAction = builder.build({ + action: Actions.update, + payload: { + attachment: persistableStateAttachment, }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - Object { - "id": "test-id", - "name": "associated-cases-comments", - "type": "cases-comments", - }, - ], - } - `); - }); + attachmentId: 'test-id', + ...commonArgs, + }); - it('builds an external reference attachment (savedObject) user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.comment)!; - const userAction = builder.build({ - action: Actions.update, - payload: { - attachment: externalReferenceAttachmentSO, - }, - attachmentId: 'test-id', - ...commonArgs, + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "comment": Object { + "owner": "securitySolutionFixture", + "persistableStateAttachmentState": Object { + "foo": "foo", + }, + "persistableStateAttachmentTypeId": ".test", + "type": "persistableState", + }, + }, + "type": "comment", + }, + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "test-id", + "name": "associated-cases-comments", + "type": "cases-comments", + }, + Object { + "id": "testRef", + "name": "myTestReference", + "type": "test-so", + }, + ], + } + `); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "action": "update", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", + it('builds a description user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.description)!; + const userAction = builder.build({ + payload: { description: 'test' }, + ...commonArgs, + }); + + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "description": "test", + }, + "type": "description", }, - "owner": "securitySolution", - "payload": Object { - "comment": Object { - "externalReferenceAttachmentTypeId": ".test", - "externalReferenceMetadata": null, - "externalReferenceStorage": Object { - "soType": "test-so", - "type": "savedObject", + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + ], + } + `); + }); + + it('builds a pushed user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.pushed)!; + const userAction = builder.build({ + payload: { externalService }, + ...commonArgs, + }); + + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "push_to_service", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "externalService": Object { + "connector_name": "ServiceNow SN", + "external_id": "external-id", + "external_title": "SIR0010037", + "external_url": "https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id", + "pushed_at": "2021-02-03T17:41:26.108Z", + "pushed_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic", + "username": "elastic", + }, }, - "owner": "securitySolution", - "type": "externalReference", }, + "type": "pushed", }, - "type": "comment", - }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "456", + "name": "pushConnectorId", + "type": "action", + }, + ], + } + `); + }); + + it('builds a tags user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.tags)!; + const userAction = builder.build({ + action: Actions.add, + payload: { tags: ['one', 'two'] }, + ...commonArgs, + }); + + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "add", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "tags": Array [ + "one", + "two", + ], + }, + "type": "tags", }, - Object { - "id": "test-id", - "name": "associated-cases-comments", - "type": "cases-comments", + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + ], + } + `); + }); + + it('builds a status user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.status)!; + const userAction = builder.build({ + payload: { status: CaseStatuses.open }, + ...commonArgs, + }); + + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "status": "open", + }, + "type": "status", }, - Object { - "id": "my-id", - "name": "externalReferenceId", - "type": "test-so", + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + ], + } + `); + }); + + it('builds a severity user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.severity)!; + const userAction = builder.build({ + payload: { severity: CaseSeverity.LOW }, + ...commonArgs, + }); + + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "severity": "low", + }, + "type": "severity", }, - ], - } - `); - }); + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + ], + } + `); + }); + + it('builds an assign user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.assignees)!; + const userAction = builder.build({ + payload: { assignees: [{ uid: '1' }, { uid: '2' }] }, + ...commonArgs, + }); - it('builds an external reference attachment (elasticSearchDoc) user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.comment)!; - const userAction = builder.build({ - action: Actions.update, - payload: { - attachment: externalReferenceAttachmentES, - }, - attachmentId: 'test-id', - ...commonArgs, + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "add", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "assignees": Array [ + Object { + "uid": "1", + }, + Object { + "uid": "2", + }, + ], + }, + "type": "assignees", + }, + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + ], + } + `); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "action": "update", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", + it('builds a settings user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.settings)!; + const userAction = builder.build({ + payload: { settings: { syncAlerts: true } }, + ...commonArgs, + }); + + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "update", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "settings": Object { + "syncAlerts": true, + }, + }, + "type": "settings", }, - "owner": "securitySolution", - "payload": Object { - "comment": Object { - "externalReferenceAttachmentTypeId": ".test", - "externalReferenceId": "my-id", - "externalReferenceMetadata": null, - "externalReferenceStorage": Object { - "type": "elasticSearchDoc", + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + ], + } + `); + }); + + it('builds a create case user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.create_case)!; + const userAction = builder.build({ + payload: casePayload, + ...commonArgs, + }); + + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "create", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object { + "assignees": Array [ + Object { + "uid": "1", + }, + ], + "connector": Object { + "fields": Object { + "category": "Denial of Service", + "destIp": true, + "malwareHash": true, + "malwareUrl": true, + "priority": "2", + "sourceIp": true, + "subcategory": "45", + }, + "name": "ServiceNow SN", + "type": ".servicenow-sir", }, + "description": "testing sir", "owner": "securitySolution", - "type": "externalReference", + "settings": Object { + "syncAlerts": true, + }, + "severity": "low", + "status": "open", + "tags": Array [ + "sir", + ], + "title": "Case SIR", }, + "type": "create_case", }, - "type": "comment", - }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - Object { - "id": "test-id", - "name": "associated-cases-comments", - "type": "cases-comments", + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "456", + "name": "connectorId", + "type": "action", + }, + ], + } + `); + }); + + it('builds a delete case user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.delete_case)!; + const userAction = builder.build({ + payload: {}, + connectorId: '456', + ...commonArgs, + }); + + expect(userAction.parameters).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "action": "delete", + "created_at": "2022-01-09T22:00:00.000Z", + "created_by": Object { + "email": "elastic@elastic.co", + "full_name": "Elastic User", + "username": "elastic", + }, + "owner": "securitySolution", + "payload": Object {}, + "type": "delete_case", }, - ], - } - `); + "references": Array [ + Object { + "id": "123", + "name": "associated-cases", + "type": "cases", + }, + Object { + "id": "456", + "name": "connectorId", + "type": "action", + }, + ], + } + `); + }); }); - it('builds a persistable state attachment user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.comment)!; - const userAction = builder.build({ - action: Actions.update, - payload: { - attachment: persistableStateAttachment, - }, - attachmentId: 'test-id', - ...commonArgs, - }); + describe('eventDetails', () => { + it('builds a title user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.title)!; + const userAction = builder.build({ + payload: { title: 'test' }, + ...commonArgs, + }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { "action": "update", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object { - "comment": Object { - "owner": "securitySolutionFixture", - "persistableStateAttachmentState": Object { - "foo": "foo", - }, - "persistableStateAttachmentTypeId": ".test", - "type": "persistableState", + "descriptiveAction": "case_user_action_update_case_title", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User updated the title for case id: 123 - user action id: 123"` + ); + }); + + it('logs a connector user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.connector)!; + const userAction = builder.build({ + payload: { + connector: { + id: '456', + name: 'ServiceNow SN', + type: ConnectorTypes.serviceNowSIR, + fields: { + category: 'Denial of Service', + destIp: true, + malwareHash: true, + malwareUrl: true, + priority: '2', + sourceIp: true, + subcategory: '45', }, }, - "type": "comment", }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - Object { - "id": "test-id", - "name": "associated-cases-comments", - "type": "cases-comments", - }, - Object { - "id": "testRef", - "name": "myTestReference", - "type": "test-so", - }, - ], - } - `); - }); + ...commonArgs, + }); - it('builds a description user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.description)!; - const userAction = builder.build({ - payload: { description: 'test' }, - ...commonArgs, + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "update", + "descriptiveAction": "case_user_action_update_case_connector", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User changed the case connector to id: 456 for case id: 123 - user action id: 123"` + ); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "action": "update", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object { - "description": "test", + it('logs a comment user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.comment)!; + const userAction = builder.build({ + action: Actions.update, + payload: { + attachment: { + comment: 'a comment!', + type: CommentType.user, + owner: SECURITY_SOLUTION_OWNER, }, - "type": "description", }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - ], - } - `); - }); + attachmentId: 'test-id', + ...commonArgs, + }); - it('builds a pushed user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.pushed)!; - const userAction = builder.build({ - payload: { externalService }, - ...commonArgs, + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "update", + "descriptiveAction": "case_user_action_update_comment", + "getMessage": [Function], + "savedObjectId": "test-id", + "savedObjectType": "cases-comments", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User changed comment id: test-id for case id: 123 - user action id: 123"` + ); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "action": "push_to_service", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object { - "externalService": Object { - "connector_name": "ServiceNow SN", - "external_id": "external-id", - "external_title": "SIR0010037", - "external_url": "https://dev92273.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=external-id", - "pushed_at": "2021-02-03T17:41:26.108Z", - "pushed_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic", - "username": "elastic", - }, - }, - }, - "type": "pushed", + it('logs an external reference attachment (savedObject) user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.comment)!; + const userAction = builder.build({ + action: Actions.create, + payload: { + attachment: externalReferenceAttachmentSO, }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - Object { - "id": "456", - "name": "pushConnectorId", - "type": "action", - }, - ], - } - `); - }); + attachmentId: 'test-id', + ...commonArgs, + }); - it('builds a tags user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.tags)!; - const userAction = builder.build({ - action: Actions.add, - payload: { tags: ['one', 'two'] }, - ...commonArgs, + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "create", + "descriptiveAction": "case_user_action_create_comment", + "getMessage": [Function], + "savedObjectId": "test-id", + "savedObjectType": "cases-comments", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User created comment id: test-id for case id: 123 - user action id: 123"` + ); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "action": "add", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object { - "tags": Array [ - "one", - "two", - ], - }, - "type": "tags", + it('logs an external reference attachment (elasticSearchDoc) user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.comment)!; + const userAction = builder.build({ + action: Actions.update, + payload: { + attachment: externalReferenceAttachmentES, }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - ], - } - `); - }); + attachmentId: 'test-id', + ...commonArgs, + }); - it('builds a status user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.status)!; - const userAction = builder.build({ - payload: { status: CaseStatuses.open }, - ...commonArgs, + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "update", + "descriptiveAction": "case_user_action_update_comment", + "getMessage": [Function], + "savedObjectId": "test-id", + "savedObjectType": "cases-comments", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User changed comment id: test-id for case id: 123 - user action id: 123"` + ); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "action": "update", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object { - "status": "open", - }, - "type": "status", + it('logs a persistable state attachment user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.comment)!; + const userAction = builder.build({ + action: Actions.update, + payload: { + attachment: persistableStateAttachment, }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - ], - } - `); - }); + attachmentId: 'test-id', + ...commonArgs, + }); - it('builds a severity user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.severity)!; - const userAction = builder.build({ - payload: { severity: CaseSeverity.LOW }, - ...commonArgs, + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "update", + "descriptiveAction": "case_user_action_update_comment", + "getMessage": [Function], + "savedObjectId": "test-id", + "savedObjectType": "cases-comments", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User changed comment id: test-id for case id: 123 - user action id: 123"` + ); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { + it('logs a description user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.description)!; + const userAction = builder.build({ + payload: { description: 'test' }, + ...commonArgs, + }); + + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { "action": "update", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object { - "severity": "low", - }, - "type": "severity", - }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - ], - } - `); - }); + "descriptiveAction": "case_user_action_update_case_description", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User updated the description for case id: 123 - user action id: 123"` + ); + }); + + it('logs a pushed user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.pushed)!; + const userAction = builder.build({ + payload: { externalService }, + ...commonArgs, + }); - it('builds an assign user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.assignees)!; - const userAction = builder.build({ - payload: { assignees: [{ uid: '1' }, { uid: '2' }] }, - ...commonArgs, + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "push_to_service", + "descriptiveAction": "case_user_action_pushed_case", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User pushed case id: 123 to an external service with connector id: 456 - user action id: 123"` + ); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { + it('logs a tags user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.tags)!; + const userAction = builder.build({ + action: Actions.add, + payload: { tags: ['one', 'two'] }, + ...commonArgs, + }); + + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { "action": "add", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object { - "assignees": Array [ - Object { - "uid": "1", - }, - Object { - "uid": "2", - }, - ], - }, - "type": "assignees", - }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - ], - } - `); - }); + "descriptiveAction": "case_user_action_add_case_tags", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User added tags to case id: 123 - user action id: 123"` + ); + }); - it('builds a settings user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.settings)!; - const userAction = builder.build({ - payload: { settings: { syncAlerts: true } }, - ...commonArgs, + it('logs a tags change user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.tags)!; + const userAction = builder.build({ + action: Actions.update, + payload: { tags: ['one', 'two'] }, + ...commonArgs, + }); + + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "update", + "descriptiveAction": "case_user_action_update_case_tags", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User changed tags for case id: 123 - user action id: 123"` + ); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { + it('logs a tags delete user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.tags)!; + const userAction = builder.build({ + action: Actions.delete, + payload: { tags: ['one', 'two'] }, + ...commonArgs, + }); + + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "delete", + "descriptiveAction": "case_user_action_delete_case_tags", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User deleted tags in case id: 123 - user action id: 123"` + ); + }); + + it('logs a status user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.status)!; + const userAction = builder.build({ + payload: { status: CaseStatuses.open }, + ...commonArgs, + }); + + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { "action": "update", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object { - "settings": Object { - "syncAlerts": true, - }, - }, - "type": "settings", - }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - ], - } - `); - }); + "descriptiveAction": "case_user_action_update_case_status", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User updated the status for case id: 123 - user action id: 123"` + ); + }); + + it('logs a severity user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.severity)!; + const userAction = builder.build({ + payload: { severity: CaseSeverity.LOW }, + ...commonArgs, + }); + + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "update", + "descriptiveAction": "case_user_action_update_case_severity", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User updated the severity for case id: 123 - user action id: 123"` + ); + }); + + it('logs an assign user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.assignees)!; + const userAction = builder.build({ + payload: { assignees: [{ uid: '1' }, { uid: '2' }] }, + ...commonArgs, + }); - it('builds a create case user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.create_case)!; - const userAction = builder.build({ - payload: casePayload, - ...commonArgs, + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "add", + "descriptiveAction": "case_user_action_add_case_assignees", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User assigned uids: [1,2] to case id: 123 - user action id: 123"` + ); + }); + + it('logs an unassign user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.assignees)!; + const userAction = builder.build({ + payload: { assignees: [{ uid: '1' }, { uid: '2' }] }, + ...commonArgs, + action: Actions.delete, + }); + + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "delete", + "descriptiveAction": "case_user_action_delete_case_assignees", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User unassigned uids: [1,2] from case id: 123 - user action id: 123"` + ); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { + it('logs an assignee unknown action user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.assignees)!; + const userAction = builder.build({ + payload: { assignees: [{ uid: '1' }, { uid: '2' }] }, + ...commonArgs, + action: Actions.create, + }); + + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { "action": "create", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object { - "assignees": Array [ - Object { - "uid": "1", - }, - ], - "connector": Object { - "fields": Object { - "category": "Denial of Service", - "destIp": true, - "malwareHash": true, - "malwareUrl": true, - "priority": "2", - "sourceIp": true, - "subcategory": "45", - }, - "name": "ServiceNow SN", - "type": ".servicenow-sir", - }, - "description": "testing sir", - "owner": "securitySolution", - "settings": Object { - "syncAlerts": true, - }, - "severity": "low", - "status": "open", - "tags": Array [ - "sir", - ], - "title": "Case SIR", - }, - "type": "create_case", - }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - Object { - "id": "456", - "name": "connectorId", - "type": "action", - }, - ], - } - `); - }); + "descriptiveAction": "case_user_action_create_case_assignees", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User changed uids: [1,2] for case id: 123 - user action id: 123"` + ); + }); + + it('logs a settings user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.settings)!; + const userAction = builder.build({ + payload: { settings: { syncAlerts: true } }, + ...commonArgs, + }); + + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "update", + "descriptiveAction": "case_user_action_update_case_settings", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User updated the settings for case id: 123 - user action id: 123"` + ); + }); + + it('logs a create case user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.create_case)!; + const userAction = builder.build({ + payload: casePayload, + ...commonArgs, + }); - it('builds a delete case user action correctly', () => { - const builder = builderFactory.getBuilder(ActionTypes.delete_case)!; - const userAction = builder.build({ - payload: {}, - connectorId: '456', - ...commonArgs, + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { + "action": "create", + "descriptiveAction": "case_user_action_create_case", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User created case id: 123 - user action id: 123"` + ); }); - expect(userAction).toMatchInlineSnapshot(` - Object { - "attributes": Object { + it('logs a delete case user action correctly', () => { + const builder = builderFactory.getBuilder(ActionTypes.delete_case)!; + const userAction = builder.build({ + payload: {}, + connectorId: '456', + ...commonArgs, + }); + + expect(userAction.eventDetails).toMatchInlineSnapshot(` + Object { "action": "delete", - "created_at": "2022-01-09T22:00:00.000Z", - "created_by": Object { - "email": "elastic@elastic.co", - "full_name": "Elastic User", - "username": "elastic", - }, - "owner": "securitySolution", - "payload": Object {}, - "type": "delete_case", - }, - "references": Array [ - Object { - "id": "123", - "name": "associated-cases", - "type": "cases", - }, - Object { - "id": "456", - "name": "connectorId", - "type": "action", - }, - ], - } - `); + "descriptiveAction": "case_user_action_delete_case", + "getMessage": [Function], + "savedObjectId": "123", + "savedObjectType": "cases", + } + `); + expect(userAction.eventDetails.getMessage('123')).toMatchInlineSnapshot( + `"User deleted case id: 123"` + ); + }); }); }); diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/assignees.ts b/x-pack/plugins/cases/server/services/user_actions/builders/assignees.ts index 554f70de71bcf..ffd695e62df37 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/assignees.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/assignees.ts @@ -5,18 +5,54 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; +import type { UserAction } from '../../../../common/api'; import { ActionTypes, Actions } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; export class AssigneesUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'assignees'>): BuilderReturnValue { - return this.buildCommonUserAction({ + build(args: UserActionParameters<'assignees'>): UserActionEvent { + const action = args.action ?? Actions.add; + + const soParams = this.buildCommonUserAction({ ...args, - action: args.action ?? Actions.add, + action, valueKey: 'assignees', value: args.payload.assignees, type: ActionTypes.assignees, }); + + const uids = args.payload.assignees.map((assignee) => assignee.uid); + const verbMessage = getVerbMessage(action, uids); + + const getMessage = (id?: string) => + `User ${verbMessage} case id: ${args.caseId} - user action id: ${id}`; + + const event: EventDetails = { + getMessage, + action, + descriptiveAction: `case_user_action_${action}_case_assignees`, + savedObjectId: args.caseId, + savedObjectType: CASE_SAVED_OBJECT, + }; + + return { + parameters: soParams, + eventDetails: event, + }; } } + +const getVerbMessage = (action: UserAction, uids: string[]) => { + const uidText = `uids: [${uids}]`; + + switch (action) { + case 'add': + return `assigned ${uidText} to`; + case 'delete': + return `unassigned ${uidText} from`; + default: + return `changed ${uidText} for`; + } +}; diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/audit_logger_utils.ts b/x-pack/plugins/cases/server/services/user_actions/builders/audit_logger_utils.ts new file mode 100644 index 0000000000000..4010c358ce601 --- /dev/null +++ b/x-pack/plugins/cases/server/services/user_actions/builders/audit_logger_utils.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { UserAction as Action } from '../../../../common/api'; + +const actionsToVerbs: Record = { + add: 'added', + delete: 'deleted', + create: 'created', + push_to_service: 'pushed', + update: 'changed', +}; + +export const getPastTenseVerb = (action: Action): string => actionsToVerbs[action]; diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/comment.ts b/x-pack/plugins/cases/server/services/user_actions/builders/comment.ts index a90a8b43b40ae..cce9fe13d4c48 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/comment.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/comment.ts @@ -6,15 +6,17 @@ */ import { uniqBy } from 'lodash'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../../../common/constants'; import { extractPersistableStateReferencesFromSO } from '../../../attachment_framework/so_references'; import type { CommentUserAction } from '../../../../common/api'; import { ActionTypes, Actions } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; import { getAttachmentSOExtractor } from '../../so_references'; +import { getPastTenseVerb } from './audit_logger_utils'; export class CommentUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'comment'>): BuilderReturnValue { + build(args: UserActionParameters<'comment'>): UserActionEvent { const soExtractor = getAttachmentSOExtractor(args.payload.attachment); const { transformedFields, references: refsWithExternalRefId } = soExtractor.extractFieldsToReferences({ @@ -26,20 +28,46 @@ export class CommentUserActionBuilder extends UserActionBuilder { persistableStateAttachmentTypeRegistry: this.persistableStateAttachmentTypeRegistry, }); + const action = args.action ?? Actions.update; + const commentUserAction = this.buildCommonUserAction({ ...args, - action: args.action ?? Actions.update, + action, valueKey: 'comment', value: { ...transformedFields, ...extractedAttributes }, type: ActionTypes.comment, }); - return { + const parameters = { ...commentUserAction, references: uniqBy( [...commentUserAction.references, ...refsWithExternalRefId, ...extractedReferences], 'id' ), }; + + const verb = getPastTenseVerb(action); + + const getMessage = (id?: string) => + `User ${verb} comment id: ${commentId(args.attachmentId)} for case id: ${ + args.caseId + } - user action id: ${id}`; + + const eventDetails: EventDetails = { + getMessage, + action, + descriptiveAction: `case_user_action_${action}_comment`, + savedObjectId: args.attachmentId ?? args.caseId, + savedObjectType: CASE_COMMENT_SAVED_OBJECT, + }; + + return { + parameters, + eventDetails, + }; } } + +const commentId = (id?: string) => { + return id ? id : 'unknown'; +}; diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/connector.ts b/x-pack/plugins/cases/server/services/user_actions/builders/connector.ts index 0009105b964f0..a54bd4cc562e3 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/connector.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/connector.ts @@ -5,19 +5,38 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; import { Actions, ActionTypes } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; export class ConnectorUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'connector'>): BuilderReturnValue { - return this.buildCommonUserAction({ + build(args: UserActionParameters<'connector'>): UserActionEvent { + const action = Actions.update; + + const parameters = this.buildCommonUserAction({ ...args, - action: Actions.update, + action, valueKey: 'connector', value: this.extractConnectorId(args.payload.connector), type: ActionTypes.connector, connectorId: args.payload.connector.id, }); + + const getMessage = (id?: string) => + `User changed the case connector to id: ${args.payload.connector.id} for case id: ${args.caseId} - user action id: ${id}`; + + const eventDetails: EventDetails = { + getMessage, + action, + descriptiveAction: 'case_user_action_update_case_connector', + savedObjectId: args.caseId, + savedObjectType: CASE_SAVED_OBJECT, + }; + + return { + parameters, + eventDetails, + }; } } diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/create_case.ts b/x-pack/plugins/cases/server/services/user_actions/builders/create_case.ts index 6d83e9acb6383..28ba21fc81bf5 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/create_case.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/create_case.ts @@ -5,18 +5,21 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; import { Actions, ActionTypes, CaseStatuses } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; export class CreateCaseUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'create_case'>): BuilderReturnValue { + build(args: UserActionParameters<'create_case'>): UserActionEvent { const { payload, caseId, owner, user } = args; + const action = Actions.create; + const connectorWithoutId = this.extractConnectorId(payload.connector); - return { + const parameters = { attributes: { ...this.getCommonUserActionAttributes({ user, owner }), - action: Actions.create, + action, payload: { ...payload, connector: connectorWithoutId, status: CaseStatuses.open }, type: ActionTypes.create_case, }, @@ -25,5 +28,20 @@ export class CreateCaseUserActionBuilder extends UserActionBuilder { ...this.createConnectorReference(payload.connector.id), ], }; + + const getMessage = (id?: string) => `User created case id: ${caseId} - user action id: ${id}`; + + const eventDetails: EventDetails = { + getMessage, + action, + descriptiveAction: 'case_user_action_create_case', + savedObjectId: caseId, + savedObjectType: CASE_SAVED_OBJECT, + }; + + return { + parameters, + eventDetails, + }; } } diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/delete_case.ts b/x-pack/plugins/cases/server/services/user_actions/builders/delete_case.ts index 2200fcc0af08b..75ec19ef22112 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/delete_case.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/delete_case.ts @@ -5,17 +5,26 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; +import type { UserAction } from '../../../../common/api'; import { Actions, ActionTypes } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { + SavedObjectParameters, + EventDetails, + UserActionParameters, + UserActionEvent, +} from '../types'; export class DeleteCaseUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'delete_case'>): BuilderReturnValue { + build(args: UserActionParameters<'delete_case'>): UserActionEvent { const { caseId, owner, user, connectorId } = args; - return { + const action = Actions.delete; + + const parameters: SavedObjectParameters = { attributes: { ...this.getCommonUserActionAttributes({ user, owner }), - action: Actions.delete, + action, payload: {}, type: ActionTypes.delete_case, }, @@ -24,5 +33,24 @@ export class DeleteCaseUserActionBuilder extends UserActionBuilder { ...this.createConnectorReference(connectorId ?? null), ], }; + + return { + parameters, + eventDetails: createDeleteEvent({ caseId, action }), + }; } } + +export const createDeleteEvent = ({ + caseId, + action, +}: { + caseId: string; + action: UserAction; +}): EventDetails => ({ + getMessage: () => `User deleted case id: ${caseId}`, + action, + descriptiveAction: 'case_user_action_delete_case', + savedObjectId: caseId, + savedObjectType: CASE_SAVED_OBJECT, +}); diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/description.ts b/x-pack/plugins/cases/server/services/user_actions/builders/description.ts index 95e506066ca6e..6f59694d83ab5 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/description.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/description.ts @@ -5,18 +5,37 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; import { Actions, ActionTypes } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; export class DescriptionUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'description'>): BuilderReturnValue { - return this.buildCommonUserAction({ + build(args: UserActionParameters<'description'>): UserActionEvent { + const action = Actions.update; + + const parameters = this.buildCommonUserAction({ ...args, - action: Actions.update, + action, valueKey: 'description', type: ActionTypes.description, value: args.payload.description, }); + + const getMessage = (id?: string) => + `User updated the description for case id: ${args.caseId} - user action id: ${id}`; + + const eventDetails: EventDetails = { + getMessage, + action, + descriptiveAction: 'case_user_action_update_case_description', + savedObjectId: args.caseId, + savedObjectType: CASE_SAVED_OBJECT, + }; + + return { + parameters, + eventDetails, + }; } } diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/pushed.ts b/x-pack/plugins/cases/server/services/user_actions/builders/pushed.ts index 75a53a79de907..5c9fa6a73007f 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/pushed.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/pushed.ts @@ -5,19 +5,38 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; import { Actions, ActionTypes } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; export class PushedUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'pushed'>): BuilderReturnValue { - return this.buildCommonUserAction({ + build(args: UserActionParameters<'pushed'>): UserActionEvent { + const action = Actions.push_to_service; + + const parameters = this.buildCommonUserAction({ ...args, - action: Actions.push_to_service, + action, valueKey: 'externalService', value: this.extractConnectorIdFromExternalService(args.payload.externalService), type: ActionTypes.pushed, connectorId: args.payload.externalService.connector_id, }); + + const getMessage = (id?: string) => + `User pushed case id: ${args.caseId} to an external service with connector id: ${args.payload.externalService.connector_id} - user action id: ${id}`; + + const eventDetails: EventDetails = { + getMessage, + action, + descriptiveAction: 'case_user_action_pushed_case', + savedObjectId: args.caseId, + savedObjectType: CASE_SAVED_OBJECT, + }; + + return { + parameters, + eventDetails, + }; } } diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/settings.ts b/x-pack/plugins/cases/server/services/user_actions/builders/settings.ts index d70f099539d65..0e3d4088fe6e5 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/settings.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/settings.ts @@ -5,18 +5,37 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; import { Actions, ActionTypes } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; export class SettingsUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'settings'>): BuilderReturnValue { - return this.buildCommonUserAction({ + build(args: UserActionParameters<'settings'>): UserActionEvent { + const action = Actions.update; + + const parameters = this.buildCommonUserAction({ ...args, - action: Actions.update, + action, valueKey: 'settings', value: args.payload.settings, type: ActionTypes.settings, }); + + const getMessage = (id?: string) => + `User updated the settings for case id: ${args.caseId} - user action id: ${id}`; + + const eventDetails: EventDetails = { + getMessage, + action, + descriptiveAction: 'case_user_action_update_case_settings', + savedObjectId: args.caseId, + savedObjectType: CASE_SAVED_OBJECT, + }; + + return { + parameters, + eventDetails, + }; } } diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/severity.ts b/x-pack/plugins/cases/server/services/user_actions/builders/severity.ts index 480fc6ffc5014..0319187d4c7ba 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/severity.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/severity.ts @@ -5,18 +5,37 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; import { Actions, ActionTypes } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; export class SeverityUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'severity'>): BuilderReturnValue { - return this.buildCommonUserAction({ + build(args: UserActionParameters<'severity'>): UserActionEvent { + const action = Actions.update; + + const parameters = this.buildCommonUserAction({ ...args, - action: Actions.update, + action, valueKey: 'severity', value: args.payload.severity, type: ActionTypes.severity, }); + + const getMessage = (id?: string) => + `User updated the severity for case id: ${args.caseId} - user action id: ${id}`; + + const eventDetails: EventDetails = { + getMessage, + action, + descriptiveAction: 'case_user_action_update_case_severity', + savedObjectId: args.caseId, + savedObjectType: CASE_SAVED_OBJECT, + }; + + return { + parameters, + eventDetails, + }; } } diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/status.ts b/x-pack/plugins/cases/server/services/user_actions/builders/status.ts index 1b3eaa9455d6b..ae2f6ff61bc94 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/status.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/status.ts @@ -5,18 +5,37 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; import { Actions, ActionTypes } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; export class StatusUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'status'>): BuilderReturnValue { - return this.buildCommonUserAction({ + build(args: UserActionParameters<'status'>): UserActionEvent { + const action = Actions.update; + + const parameters = this.buildCommonUserAction({ ...args, - action: Actions.update, + action, valueKey: 'status', value: args.payload.status, type: ActionTypes.status, }); + + const getMessage = (id?: string) => + `User updated the status for case id: ${args.caseId} - user action id: ${id}`; + + const eventDetails: EventDetails = { + getMessage, + action, + descriptiveAction: 'case_user_action_update_case_status', + savedObjectId: args.caseId, + savedObjectType: CASE_SAVED_OBJECT, + }; + + return { + parameters, + eventDetails, + }; } } diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/tags.ts b/x-pack/plugins/cases/server/services/user_actions/builders/tags.ts index 053c7d3fa91de..1a9066cbbf224 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/tags.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/tags.ts @@ -5,18 +5,53 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; +import type { UserAction } from '../../../../common/api'; import { ActionTypes, Actions } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { UserActionParameters, BuilderReturnValue } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; +import { getPastTenseVerb } from './audit_logger_utils'; export class TagsUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'tags'>): BuilderReturnValue { - return this.buildCommonUserAction({ + build(args: UserActionParameters<'tags'>): UserActionEvent { + const action = args.action ?? Actions.add; + + const parameters = this.buildCommonUserAction({ ...args, action: args.action ?? Actions.add, valueKey: 'tags', value: args.payload.tags, type: ActionTypes.tags, }); + + const verb = getPastTenseVerb(action); + const preposition = getPreposition(action); + + const getMessage = (id?: string) => + `User ${verb} tags ${preposition} case id: ${args.caseId} - user action id: ${id}`; + + const eventDetails: EventDetails = { + getMessage, + action, + descriptiveAction: `case_user_action_${action}_case_tags`, + savedObjectId: args.caseId, + savedObjectType: CASE_SAVED_OBJECT, + }; + + return { + parameters, + eventDetails, + }; } } + +const getPreposition = (action: UserAction): string => { + switch (action) { + case Actions.add: + return 'to'; + case Actions.delete: + return 'in'; + default: + return 'for'; + } +}; diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/title.ts b/x-pack/plugins/cases/server/services/user_actions/builders/title.ts index 3d5251d84b629..1802c38943d3b 100644 --- a/x-pack/plugins/cases/server/services/user_actions/builders/title.ts +++ b/x-pack/plugins/cases/server/services/user_actions/builders/title.ts @@ -5,18 +5,37 @@ * 2.0. */ +import { CASE_SAVED_OBJECT } from '../../../../common/constants'; import { Actions, ActionTypes } from '../../../../common/api'; import { UserActionBuilder } from '../abstract_builder'; -import type { BuilderReturnValue, UserActionParameters } from '../types'; +import type { EventDetails, UserActionParameters, UserActionEvent } from '../types'; export class TitleUserActionBuilder extends UserActionBuilder { - build(args: UserActionParameters<'title'>): BuilderReturnValue { - return this.buildCommonUserAction({ + build(args: UserActionParameters<'title'>): UserActionEvent { + const action = Actions.update; + + const parameters = this.buildCommonUserAction({ ...args, - action: Actions.update, + action, valueKey: 'title', value: args.payload.title, type: ActionTypes.title, }); + + const getMessage = (id?: string) => + `User updated the title for case id: ${args.caseId} - user action id: ${id}`; + + const eventDetails: EventDetails = { + getMessage, + action, + descriptiveAction: 'case_user_action_update_case_title', + savedObjectId: args.caseId, + savedObjectType: CASE_SAVED_OBJECT, + }; + + return { + parameters, + eventDetails, + }; } } diff --git a/x-pack/plugins/cases/server/services/user_actions/index.test.ts b/x-pack/plugins/cases/server/services/user_actions/index.test.ts index bf4ee5a1ea7c2..5ba80b05f7194 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.test.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.test.ts @@ -11,11 +11,13 @@ import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import type { SavedObject, SavedObjectReference, + SavedObjectsBulkCreateObject, SavedObjectsFindResponse, SavedObjectsFindResult, SavedObjectsUpdateResponse, } from '@kbn/core/server'; import { ACTION_SAVED_OBJECT_TYPE } from '@kbn/actions-plugin/server'; +import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import type { CaseAttributes, CaseUserActionAttributes, @@ -635,17 +637,38 @@ describe('CaseUserActionService', () => { describe('methods', () => { let service: CaseUserActionService; const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); + unsecuredSavedObjectsClient.create.mockResolvedValue({ + id: 'created_user_action_id', + } as SavedObject); + + unsecuredSavedObjectsClient.bulkCreate.mockImplementation( + async (objects: SavedObjectsBulkCreateObject[]) => { + const savedObjects: SavedObject[] = []; + for (let i = 0; i < objects.length; i++) { + savedObjects.push({ id: i } as unknown as SavedObject); + } + + return { + saved_objects: savedObjects, + }; + } + ); const mockLogger = loggerMock.create(); const commonArgs = { - unsecuredSavedObjectsClient, caseId: '123', user: { full_name: 'Elastic User', username: 'elastic', email: 'elastic@elastic.co' }, owner: SECURITY_SOLUTION_OWNER, }; + const mockAuditLogger = auditLoggerMock.create(); beforeEach(() => { jest.clearAllMocks(); - service = new CaseUserActionService(mockLogger, persistableStateAttachmentTypeRegistry); + service = new CaseUserActionService({ + unsecuredSavedObjectsClient, + log: mockLogger, + persistableStateAttachmentTypeRegistry, + auditLogger: mockAuditLogger, + }); }); describe('createUserAction', () => { @@ -702,6 +725,38 @@ describe('CaseUserActionService', () => { ); }); + it('logs a create case user action', async () => { + await service.createUserAction({ + ...commonArgs, + payload: casePayload, + type: ActionTypes.create_case, + }); + + expect(mockAuditLogger.log).toBeCalledTimes(1); + expect(mockAuditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "event": Object { + "action": "case_user_action_create_case", + "category": Array [ + "database", + ], + "type": Array [ + "creation", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "123", + "type": "cases", + }, + }, + "message": "User created case id: 123 - user action id: created_user_action_id", + }, + ] + `); + }); + describe('status', () => { it('creates an update status user action', async () => { await service.createUserAction({ @@ -727,6 +782,38 @@ describe('CaseUserActionService', () => { { references: [{ id: '123', name: 'associated-cases', type: 'cases' }] } ); }); + + it('logs an update status user action', async () => { + await service.createUserAction({ + ...commonArgs, + payload: { status: CaseStatuses.closed }, + type: ActionTypes.status, + }); + + expect(mockAuditLogger.log).toBeCalledTimes(1); + expect(mockAuditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "event": Object { + "action": "case_user_action_update_case_status", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "123", + "type": "cases", + }, + }, + "message": "User updated the status for case id: 123 - user action id: created_user_action_id", + }, + ] + `); + }); }); describe('severity', () => { @@ -754,6 +841,38 @@ describe('CaseUserActionService', () => { { references: [{ id: '123', name: 'associated-cases', type: 'cases' }] } ); }); + + it('logs an update severity user action', async () => { + await service.createUserAction({ + ...commonArgs, + payload: { severity: CaseSeverity.MEDIUM }, + type: ActionTypes.severity, + }); + + expect(mockAuditLogger.log).toBeCalledTimes(1); + expect(mockAuditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "event": Object { + "action": "case_user_action_update_case_severity", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "123", + "type": "cases", + }, + }, + "message": "User updated the severity for case id: 123 - user action id: created_user_action_id", + }, + ] + `); + }); }); describe('push', () => { @@ -800,6 +919,38 @@ describe('CaseUserActionService', () => { } ); }); + + it('logs a push user action', async () => { + await service.createUserAction({ + ...commonArgs, + payload: { externalService }, + type: ActionTypes.pushed, + }); + + expect(mockAuditLogger.log).toBeCalledTimes(1); + expect(mockAuditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "event": Object { + "action": "case_user_action_pushed_case", + "category": Array [ + "database", + ], + "type": Array [ + "creation", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "123", + "type": "cases", + }, + }, + "message": "User pushed case id: 123 to an external service with connector id: 456 - user action id: created_user_action_id", + }, + ] + `); + }); }); describe('comment', () => { @@ -843,64 +994,77 @@ describe('CaseUserActionService', () => { ); } ); + + it.each([[Actions.create], [Actions.delete], [Actions.update]])( + 'logs a comment user action of action: %s', + async (action) => { + await service.createUserAction({ + ...commonArgs, + type: ActionTypes.comment, + action, + attachmentId: 'test-id', + payload: { attachment: comment }, + }); + + expect(mockAuditLogger.log).toBeCalledTimes(1); + expect(mockAuditLogger.log.mock.calls[0]).toMatchSnapshot(); + } + ); }); }); }); - describe('bulkCreateCaseDeletion', () => { - it('creates a delete case user action', async () => { - await service.bulkCreateCaseDeletion({ - unsecuredSavedObjectsClient, - cases: [ - { id: '1', owner: SECURITY_SOLUTION_OWNER, connectorId: '3' }, - { id: '2', owner: SECURITY_SOLUTION_OWNER, connectorId: '4' }, - ], - user: commonArgs.user, - }); + describe('bulkAuditLogCaseDeletion', () => { + it('logs a delete case audit log message', async () => { + await service.bulkAuditLogCaseDeletion(['1', '2']); - expect(unsecuredSavedObjectsClient.bulkCreate).toHaveBeenCalledWith( - [ - { - attributes: { - action: 'delete', - created_at: '2022-01-09T22:00:00.000Z', - created_by: { - email: 'elastic@elastic.co', - full_name: 'Elastic User', - username: 'elastic', + expect(unsecuredSavedObjectsClient.bulkCreate).not.toHaveBeenCalled(); + + expect(mockAuditLogger.log).toHaveBeenCalledTimes(2); + expect(mockAuditLogger.log.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "case_user_action_delete_case", + "category": Array [ + "database", + ], + "type": Array [ + "deletion", + ], }, - type: 'delete_case', - owner: 'securitySolution', - payload: {}, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "User deleted case id: 1", }, - references: [ - { id: '1', name: 'associated-cases', type: 'cases' }, - { id: '3', name: 'connectorId', type: 'action' }, - ], - type: 'cases-user-actions', - }, - { - attributes: { - action: 'delete', - created_at: '2022-01-09T22:00:00.000Z', - created_by: { - email: 'elastic@elastic.co', - full_name: 'Elastic User', - username: 'elastic', + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_delete_case", + "category": Array [ + "database", + ], + "type": Array [ + "deletion", + ], }, - type: 'delete_case', - owner: 'securitySolution', - payload: {}, + "kibana": Object { + "saved_object": Object { + "id": "2", + "type": "cases", + }, + }, + "message": "User deleted case id: 2", }, - references: [ - { id: '2', name: 'associated-cases', type: 'cases' }, - { id: '4', name: 'connectorId', type: 'action' }, - ], - type: 'cases-user-actions', - }, - ], - { refresh: undefined } - ); + ], + ] + `); }); }); @@ -1073,6 +1237,181 @@ describe('CaseUserActionService', () => { ); }); + it('logs the correct user actions when bulk updating cases', async () => { + await service.bulkCreateUpdateCase({ + ...commonArgs, + originalCases, + updatedCases, + user: commonArgs.user, + }); + + expect(mockAuditLogger.log).toBeCalledTimes(8); + expect(mockAuditLogger.log.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "case_user_action_update_case_title", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "User updated the title for case id: 1 - user action id: 0", + }, + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_update_case_status", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "User updated the status for case id: 1 - user action id: 1", + }, + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_update_case_connector", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "User changed the case connector to id: 456 for case id: 1 - user action id: 2", + }, + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_update_case_description", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "2", + "type": "cases", + }, + }, + "message": "User updated the description for case id: 2 - user action id: 3", + }, + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_add_case_tags", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "2", + "type": "cases", + }, + }, + "message": "User added tags to case id: 2 - user action id: 4", + }, + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_delete_case_tags", + "category": Array [ + "database", + ], + "type": Array [ + "deletion", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "2", + "type": "cases", + }, + }, + "message": "User deleted tags in case id: 2 - user action id: 5", + }, + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_update_case_settings", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "2", + "type": "cases", + }, + }, + "message": "User updated the settings for case id: 2 - user action id: 6", + }, + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_update_case_severity", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "2", + "type": "cases", + }, + }, + "message": "User updated the severity for case id: 2 - user action id: 7", + }, + ], + ] + `); + }); + it('creates the correct user actions when an assignee is added', async () => { await service.bulkCreateUpdateCase({ ...commonArgs, @@ -1120,6 +1459,41 @@ describe('CaseUserActionService', () => { `); }); + it('logs the correct user actions when an assignee is added', async () => { + await service.bulkCreateUpdateCase({ + ...commonArgs, + originalCases, + updatedCases: updatedAssigneesCases, + user: commonArgs.user, + }); + + expect(mockAuditLogger.log).toBeCalledTimes(1); + expect(mockAuditLogger.log.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "case_user_action_add_case_assignees", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "User assigned uids: [1] to case id: 1 - user action id: 0", + }, + ], + ] + `); + }); + it('creates the correct user actions when an assignee is removed', async () => { const casesWithAssigneeRemoved: Array> = [ { @@ -1177,6 +1551,51 @@ describe('CaseUserActionService', () => { `); }); + it('logs the correct user actions when an assignee is removed', async () => { + const casesWithAssigneeRemoved: Array> = [ + { + ...createCaseSavedObjectResponse(), + id: '1', + attributes: { + assignees: [], + }, + }, + ]; + + await service.bulkCreateUpdateCase({ + ...commonArgs, + originalCases: originalCasesWithAssignee, + updatedCases: casesWithAssigneeRemoved, + user: commonArgs.user, + }); + + expect(mockAuditLogger.log).toBeCalledTimes(1); + expect(mockAuditLogger.log.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "case_user_action_delete_case_assignees", + "category": Array [ + "database", + ], + "type": Array [ + "deletion", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "User unassigned uids: [1] from case id: 1 - user action id: 0", + }, + ], + ] + `); + }); + it('creates the correct user actions when assignees are added and removed', async () => { const caseAssignees: Array> = [ { @@ -1262,6 +1681,71 @@ describe('CaseUserActionService', () => { `); }); + it('logs the correct user actions when assignees are added and removed', async () => { + const caseAssignees: Array> = [ + { + ...createCaseSavedObjectResponse(), + id: '1', + attributes: { + assignees: [{ uid: '2' }], + }, + }, + ]; + + await service.bulkCreateUpdateCase({ + ...commonArgs, + originalCases: originalCasesWithAssignee, + updatedCases: caseAssignees, + user: commonArgs.user, + }); + + expect(mockAuditLogger.log).toBeCalledTimes(2); + expect(mockAuditLogger.log.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "case_user_action_add_case_assignees", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "User assigned uids: [2] to case id: 1 - user action id: 0", + }, + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_delete_case_assignees", + "category": Array [ + "database", + ], + "type": Array [ + "deletion", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "User unassigned uids: [1] from case id: 1 - user action id: 1", + }, + ], + ] + `); + }); + it('creates the correct user actions when tags are added and removed', async () => { await service.bulkCreateUpdateCase({ ...commonArgs, @@ -1333,6 +1817,61 @@ describe('CaseUserActionService', () => { ] `); }); + + it('logs the correct user actions when tags are added and removed', async () => { + await service.bulkCreateUpdateCase({ + ...commonArgs, + originalCases, + updatedCases: updatedTagsCases, + user: commonArgs.user, + }); + + expect(mockAuditLogger.log).toBeCalledTimes(2); + expect(mockAuditLogger.log.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "case_user_action_add_case_tags", + "category": Array [ + "database", + ], + "type": Array [ + "change", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "User added tags to case id: 1 - user action id: 0", + }, + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_delete_case_tags", + "category": Array [ + "database", + ], + "type": Array [ + "deletion", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases", + }, + }, + "message": "User deleted tags in case id: 1 - user action id: 1", + }, + ], + ] + `); + }); }); describe('bulkCreateAttachmentDeletion', () => { @@ -1395,20 +1934,58 @@ describe('CaseUserActionService', () => { { refresh: undefined } ); }); - }); - describe('create', () => { - it('creates user actions', async () => { - await service.create<{ title: string }>({ - unsecuredSavedObjectsClient, - attributes: { title: 'test' }, - references: [], + it('logs delete comment user action', async () => { + await service.bulkCreateAttachmentDeletion({ + ...commonArgs, + attachments, }); - expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith( - 'cases-user-actions', - { title: 'test' }, - { references: [] } - ); + + expect(mockAuditLogger.log).toBeCalledTimes(2); + expect(mockAuditLogger.log.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "case_user_action_delete_comment", + "category": Array [ + "database", + ], + "type": Array [ + "deletion", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases-comments", + }, + }, + "message": "User deleted comment id: 1 for case id: 123 - user action id: 0", + }, + ], + Array [ + Object { + "event": Object { + "action": "case_user_action_delete_comment", + "category": Array [ + "database", + ], + "type": Array [ + "deletion", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "2", + "type": "cases-comments", + }, + }, + "message": "User deleted comment id: 2 for case id: 123 - user action id: 1", + }, + ], + ] + `); }); }); @@ -1458,7 +2035,6 @@ describe('CaseUserActionService', () => { it('it returns an empty array if the response is not valid', async () => { const res = await service.getUniqueConnectors({ - unsecuredSavedObjectsClient, caseId: '123', }); @@ -1472,7 +2048,6 @@ describe('CaseUserActionService', () => { } as unknown as Promise); const res = await service.getUniqueConnectors({ - unsecuredSavedObjectsClient, caseId: '123', }); @@ -1485,7 +2060,6 @@ describe('CaseUserActionService', () => { it('it returns the unique connectors', async () => { await service.getUniqueConnectors({ - unsecuredSavedObjectsClient, caseId: '123', }); diff --git a/x-pack/plugins/cases/server/services/user_actions/index.ts b/x-pack/plugins/cases/server/services/user_actions/index.ts index 37642218364b9..c77aed7c6f95f 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.ts @@ -11,6 +11,7 @@ import type { Logger, SavedObject, SavedObjectReference, + SavedObjectsBulkResponse, SavedObjectsClientContract, SavedObjectsFindResponse, SavedObjectsUpdateResponse, @@ -18,6 +19,7 @@ import type { import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { KueryNode } from '@kbn/es-query'; +import type { AuditLogger } from '@kbn/security-plugin/server'; import { isCommentRequestTypePersistableState } from '../../../common/utils/attachments'; import { isConnectorUserAction, @@ -27,7 +29,6 @@ import { isCommentUserAction, } from '../../../common/utils/user_actions'; import type { - ActionOperationValues, ActionTypeValues, CaseAttributes, CaseUserActionAttributes, @@ -37,6 +38,7 @@ import type { CaseAssignees, CommentRequest, User, + UserAction as Action, } from '../../../common/api'; import { Actions, ActionTypes, NONE_CONNECTOR_ID } from '../../../common/api'; import { @@ -45,7 +47,6 @@ import { MAX_DOCS_PER_PAGE, CASE_COMMENT_SAVED_OBJECT, } from '../../../common/constants'; -import type { ClientArgs } from '..'; import { CASE_REF_NAME, COMMENT_REF_NAME, @@ -56,10 +57,11 @@ import { import { findConnectorIdReference } from '../transform'; import { buildFilter, combineFilters, arraysDifference } from '../../client/utils'; import type { + Attributes, BuilderParameters, - BuilderReturnValue, CommonArguments, CreateUserAction, + UserActionEvent, UserActionParameters, } from './types'; import { BuilderFactory } from './builder_factory'; @@ -69,31 +71,24 @@ import { injectPersistableReferencesToSO } from '../../attachment_framework/so_r import type { IndexRefresh } from '../types'; import { isAssigneesArray, isStringArray } from './type_guards'; import type { CaseSavedObject } from '../../common/types'; - -interface GetCaseUserActionArgs extends ClientArgs { - caseId: string; -} +import { UserActionAuditLogger } from './audit_logger'; +import { createDeleteEvent } from './builders/delete_case'; export interface UserActionItem { attributes: CaseUserActionAttributesWithoutConnectorId; references: SavedObjectReference[]; } -interface PostCaseUserActionArgs extends ClientArgs, IndexRefresh { - actions: BuilderReturnValue[]; +interface PostCaseUserActionArgs extends IndexRefresh { + actions: UserActionEvent[]; } -interface CreateUserActionES extends ClientArgs, IndexRefresh { +interface CreateUserActionES extends IndexRefresh { attributes: T; references: SavedObjectReference[]; } -type CommonUserActionArgs = ClientArgs & CommonArguments; - -interface BulkCreateCaseDeletionUserAction extends ClientArgs, IndexRefresh { - cases: Array<{ id: string; owner: string; connectorId: string }>; - user: User; -} +type CommonUserActionArgs = CommonArguments; interface GetUserActionItemByDifference extends CommonUserActionArgs { field: string; @@ -106,7 +101,7 @@ interface TypedUserActionDiffedItems extends GetUserActionItemByDifference { newValue: T[]; } -interface BulkCreateBulkUpdateCaseUserActions extends ClientArgs, IndexRefresh { +interface BulkCreateBulkUpdateCaseUserActions extends IndexRefresh { originalCases: CaseSavedObject[]; updatedCases: Array>; user: User; @@ -127,20 +122,35 @@ type CreatePayloadFunction = ( export class CaseUserActionService { private static readonly userActionFieldsAllowed: Set = new Set(Object.keys(ActionTypes)); + private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract; private readonly builderFactory: BuilderFactory; + private readonly persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry; + private readonly log: Logger; + private readonly auditLogger: UserActionAuditLogger; + + constructor({ + log, + persistableStateAttachmentTypeRegistry, + unsecuredSavedObjectsClient, + auditLogger, + }: { + log: Logger; + persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry; + unsecuredSavedObjectsClient: SavedObjectsClientContract; + auditLogger: AuditLogger; + }) { + this.log = log; + this.unsecuredSavedObjectsClient = unsecuredSavedObjectsClient; + this.persistableStateAttachmentTypeRegistry = persistableStateAttachmentTypeRegistry; - constructor( - private readonly log: Logger, - private readonly persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry - ) { this.builderFactory = new BuilderFactory({ persistableStateAttachmentTypeRegistry: this.persistableStateAttachmentTypeRegistry, }); + + this.auditLogger = new UserActionAuditLogger(auditLogger); } - private getUserActionItemByDifference( - params: GetUserActionItemByDifference - ): BuilderReturnValue[] { + private getUserActionItemByDifference(params: GetUserActionItemByDifference): UserActionEvent[] { const { field, originalValue, newValue, caseId, owner, user } = params; if (!CaseUserActionService.userActionFieldsAllowed.has(field)) { @@ -228,7 +238,7 @@ export class CaseUserActionService { }: { commonArgs: CommonUserActionArgs; actionType: ActionType; - action: ActionOperationValues; + action: Action; createPayload: CreatePayloadFunction; modifiedItems?: Item[] | null; }) { @@ -251,91 +261,60 @@ export class CaseUserActionService { return userAction; } - public async bulkCreateCaseDeletion({ - unsecuredSavedObjectsClient, - cases, - user, - refresh, - }: BulkCreateCaseDeletionUserAction): Promise { - this.log.debug(`Attempting to create a create case user action`); - const userActionsWithReferences = cases.reduce((acc, caseInfo) => { - const userActionBuilder = this.builderFactory.getBuilder(ActionTypes.delete_case); - const deleteCaseUserAction = userActionBuilder?.build({ - action: Actions.delete, - caseId: caseInfo.id, - user, - owner: caseInfo.owner, - connectorId: caseInfo.connectorId, - payload: {}, - }); - - if (deleteCaseUserAction == null) { - return acc; - } - - return [...acc, deleteCaseUserAction]; - }, []); + public async bulkAuditLogCaseDeletion(caseIds: string[]) { + this.log.debug(`Attempting to log bulk case deletion`); - await this.bulkCreate({ - unsecuredSavedObjectsClient, - actions: userActionsWithReferences, - refresh, - }); + for (const id of caseIds) { + this.auditLogger.log(createDeleteEvent({ caseId: id, action: Actions.delete })); + } } public async bulkCreateUpdateCase({ - unsecuredSavedObjectsClient, originalCases, updatedCases, user, refresh, }: BulkCreateBulkUpdateCaseUserActions): Promise { - const userActionsWithReferences = updatedCases.reduce( - (acc, updatedCase) => { - const originalCase = originalCases.find(({ id }) => id === updatedCase.id); + const builtUserActions = updatedCases.reduce((acc, updatedCase) => { + const originalCase = originalCases.find(({ id }) => id === updatedCase.id); - if (originalCase == null) { - return acc; - } + if (originalCase == null) { + return acc; + } - const caseId = updatedCase.id; - const owner = originalCase.attributes.owner; - - const userActions: BuilderReturnValue[] = []; - const updatedFields = Object.keys(updatedCase.attributes); - - updatedFields - .filter((field) => CaseUserActionService.userActionFieldsAllowed.has(field)) - .forEach((field) => { - const originalValue = get(originalCase, ['attributes', field]); - const newValue = get(updatedCase, ['attributes', field]); - userActions.push( - ...this.getUserActionItemByDifference({ - unsecuredSavedObjectsClient, - field, - originalValue, - newValue, - user, - owner, - caseId, - }) - ); - }); - - return [...acc, ...userActions]; - }, - [] - ); + const caseId = updatedCase.id; + const owner = originalCase.attributes.owner; + + const userActions: UserActionEvent[] = []; + const updatedFields = Object.keys(updatedCase.attributes); + + updatedFields + .filter((field) => CaseUserActionService.userActionFieldsAllowed.has(field)) + .forEach((field) => { + const originalValue = get(originalCase, ['attributes', field]); + const newValue = get(updatedCase, ['attributes', field]); + userActions.push( + ...this.getUserActionItemByDifference({ + field, + originalValue, + newValue, + user, + owner, + caseId, + }) + ); + }); - await this.bulkCreate({ - unsecuredSavedObjectsClient, - actions: userActionsWithReferences, + return [...acc, ...userActions]; + }, []); + + await this.bulkCreateAndLog({ + userActions: builtUserActions, refresh, }); } private async bulkCreateAttachment({ - unsecuredSavedObjectsClient, caseId, attachments, user, @@ -343,43 +322,37 @@ export class CaseUserActionService { refresh, }: BulkCreateAttachmentUserAction): Promise { this.log.debug(`Attempting to create a bulk create case user action`); - const userActionsWithReferences = attachments.reduce( - (acc, attachment) => { - const userActionBuilder = this.builderFactory.getBuilder(ActionTypes.comment); - const commentUserAction = userActionBuilder?.build({ - action, - caseId, - user, - owner: attachment.owner, - attachmentId: attachment.id, - payload: { attachment: attachment.attachment }, - }); + const userActions = attachments.reduce((acc, attachment) => { + const userActionBuilder = this.builderFactory.getBuilder(ActionTypes.comment); + const commentUserAction = userActionBuilder?.build({ + action, + caseId, + user, + owner: attachment.owner, + attachmentId: attachment.id, + payload: { attachment: attachment.attachment }, + }); - if (commentUserAction == null) { - return acc; - } + if (commentUserAction == null) { + return acc; + } - return [...acc, commentUserAction]; - }, - [] - ); + return [...acc, commentUserAction]; + }, []); - await this.bulkCreate({ - unsecuredSavedObjectsClient, - actions: userActionsWithReferences, + await this.bulkCreateAndLog({ + userActions, refresh, }); } public async bulkCreateAttachmentDeletion({ - unsecuredSavedObjectsClient, caseId, attachments, user, refresh, }: BulkCreateAttachmentUserAction): Promise { await this.bulkCreateAttachment({ - unsecuredSavedObjectsClient, caseId, attachments, user, @@ -389,14 +362,12 @@ export class CaseUserActionService { } public async bulkCreateAttachmentCreation({ - unsecuredSavedObjectsClient, caseId, attachments, user, refresh, }: BulkCreateAttachmentUserAction): Promise { await this.bulkCreateAttachment({ - unsecuredSavedObjectsClient, caseId, attachments, user, @@ -406,7 +377,6 @@ export class CaseUserActionService { } public async createUserAction({ - unsecuredSavedObjectsClient, action, type, caseId, @@ -432,8 +402,7 @@ export class CaseUserActionService { }); if (userAction) { - const { attributes, references } = userAction; - await this.create({ unsecuredSavedObjectsClient, attributes, references, refresh }); + await this.createAndLog({ userAction, refresh }); } } catch (error) { this.log.error(`Error on creating user action of type: ${type}. Error: ${error}`); @@ -441,16 +410,13 @@ export class CaseUserActionService { } } - public async getAll({ - unsecuredSavedObjectsClient, - caseId, - }: GetCaseUserActionArgs): Promise> { + public async getAll(caseId: string): Promise> { try { const id = caseId; const type = CASE_SAVED_OBJECT; const userActions = - await unsecuredSavedObjectsClient.find({ + await this.unsecuredSavedObjectsClient.find({ type: CASE_USER_ACTION_SAVED_OBJECT, hasReference: { type, id }, page: 1, @@ -469,30 +435,87 @@ export class CaseUserActionService { } } - public async create({ - unsecuredSavedObjectsClient, + public async getUserActionIdsForCases(caseIds: string[]) { + try { + this.log.debug(`Attempting to retrieve user actions associated with cases: [${caseIds}]`); + + const finder = this.unsecuredSavedObjectsClient.createPointInTimeFinder({ + type: CASE_USER_ACTION_SAVED_OBJECT, + hasReference: caseIds.map((id) => ({ id, type: CASE_SAVED_OBJECT })), + sortField: 'created_at', + sortOrder: 'asc', + /** + * We only care about the ids so to reduce the data returned we should limit the fields in the response. Core + * doesn't support retrieving no fields (id would always be returned anyway) so to limit it we'll only request + * the owner even though we don't need it. + */ + fields: ['owner'], + perPage: MAX_DOCS_PER_PAGE, + }); + + const ids: string[] = []; + for await (const userActionSavedObject of finder.find()) { + ids.push(...userActionSavedObject.saved_objects.map((userAction) => userAction.id)); + } + + return ids; + } catch (error) { + this.log.error(`Error retrieving user action ids for cases: [${caseIds}]: ${error}`); + throw error; + } + } + + private async createAndLog({ + userAction, + refresh, + }: { + userAction: UserActionEvent; + } & IndexRefresh): Promise { + const createdUserAction = await this.create({ ...userAction.parameters, refresh }); + this.auditLogger.log(userAction.eventDetails, createdUserAction.id); + } + + private async create({ attributes, references, refresh, - }: CreateUserActionES): Promise { + }: CreateUserActionES): Promise> { try { this.log.debug(`Attempting to POST a new case user action`); - await unsecuredSavedObjectsClient.create(CASE_USER_ACTION_SAVED_OBJECT, attributes, { - references: references ?? [], - refresh, - }); + return await this.unsecuredSavedObjectsClient.create( + CASE_USER_ACTION_SAVED_OBJECT, + attributes, + { + references: references ?? [], + refresh, + } + ); } catch (error) { this.log.error(`Error on POST a new case user action: ${error}`); throw error; } } - public async bulkCreate({ - unsecuredSavedObjectsClient, + private async bulkCreateAndLog({ + userActions, + refresh, + }: { userActions: UserActionEvent[] } & IndexRefresh) { + const createdUserActions = await this.bulkCreate({ actions: userActions, refresh }); + + if (!createdUserActions) { + return; + } + + for (let i = 0; i < userActions.length; i++) { + this.auditLogger.log(userActions[i].eventDetails, createdUserActions.saved_objects[i].id); + } + } + + private async bulkCreate({ actions, refresh, - }: PostCaseUserActionArgs): Promise { + }: PostCaseUserActionArgs): Promise | undefined> { if (isEmpty(actions)) { return; } @@ -500,8 +523,11 @@ export class CaseUserActionService { try { this.log.debug(`Attempting to POST a new case user action`); - await unsecuredSavedObjectsClient.bulkCreate( - actions.map((action) => ({ type: CASE_USER_ACTION_SAVED_OBJECT, ...action })), + return await this.unsecuredSavedObjectsClient.bulkCreate( + actions.map((action) => ({ + type: CASE_USER_ACTION_SAVED_OBJECT, + ...action.parameters, + })), { refresh } ); } catch (error) { @@ -511,11 +537,9 @@ export class CaseUserActionService { } public async findStatusChanges({ - unsecuredSavedObjectsClient, caseId, filter, }: { - unsecuredSavedObjectsClient: SavedObjectsClientContract; caseId: string; filter?: KueryNode; }): Promise>> { @@ -539,7 +563,7 @@ export class CaseUserActionService { const combinedFilters = combineFilters([updateActionFilter, statusChangeFilter, filter]); const finder = - unsecuredSavedObjectsClient.createPointInTimeFinder( + this.unsecuredSavedObjectsClient.createPointInTimeFinder( { type: CASE_USER_ACTION_SAVED_OBJECT, hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, @@ -569,10 +593,8 @@ export class CaseUserActionService { public async getUniqueConnectors({ caseId, filter, - unsecuredSavedObjectsClient, }: { caseId: string; - unsecuredSavedObjectsClient: SavedObjectsClientContract; filter?: KueryNode; }): Promise> { try { @@ -586,7 +608,7 @@ export class CaseUserActionService { const combinedFilter = combineFilters([connectorsFilter, filter]); - const response = await unsecuredSavedObjectsClient.find< + const response = await this.unsecuredSavedObjectsClient.find< CaseUserActionAttributesWithoutConnectorId, { references: { connectors: { ids: { buckets: Array<{ key: string }> } } } } >({ diff --git a/x-pack/plugins/cases/server/services/user_actions/types.ts b/x-pack/plugins/cases/server/services/user_actions/types.ts index 153c09704aaa7..be3fc602c132a 100644 --- a/x-pack/plugins/cases/server/services/user_actions/types.ts +++ b/x-pack/plugins/cases/server/services/user_actions/types.ts @@ -98,11 +98,24 @@ export interface Attributes { payload: Record; } -export interface BuilderReturnValue { +export interface SavedObjectParameters { attributes: Attributes; references: SavedObjectReference[]; } +export interface EventDetails { + getMessage: (storedUserActionId?: string) => string; + action: UserAction; + descriptiveAction: string; + savedObjectId: string; + savedObjectType: string; +} + +export interface UserActionEvent { + parameters: SavedObjectParameters; + eventDetails: EventDetails; +} + export type CommonBuilderArguments = CommonArguments & { action: UserAction; type: UserActionTypes; diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts index dc2afe6e41957..d3bf05d486d82 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts @@ -293,5 +293,32 @@ describe('#setupSavedObjects', () => { expect(res.saved_objects[0].error).toHaveProperty('message', 'Test failure'); } }); + + it('properly re-exposes `close` method of the underlying point in time finder ', async () => { + // The finder that underlying repository returns is an instance of a `PointInTimeFinder` class that cannot, and + // unlike object literal it cannot be "copied" with the spread operator. We should make sure we properly re-expose + // `close` function. + const mockClose = jest.fn(); + mockSavedObjectsRepository.createPointInTimeFinder = jest.fn().mockImplementation(() => { + class MockPointInTimeFinder { + async close() { + mockClose(); + } + async *find() {} + } + + return new MockPointInTimeFinder(); + }); + + const finder = await setupContract().createPointInTimeFinderDecryptedAsInternalUser({ + type: 'known-type', + }); + + expect(finder.find).toBeInstanceOf(Function); + expect(finder.close).toBeInstanceOf(Function); + + await finder.close(); + expect(mockClose).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts index 6be6fae9f5d31..b7a7da6ba7585 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts @@ -177,7 +177,7 @@ export function setupSavedObjects({ } } - return { ...finder, find: () => encryptedFinder() }; + return { find: () => encryptedFinder(), close: finder.close.bind(finder) }; }, }; }; diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index ff30b1fcd3f59..c5bb030a7d997 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -175,7 +175,6 @@ export const INSTALL_SCRIPT_API_ROUTES = `${API_ROOT}/install/{osType}`; // Policy preconfig API routes export const PRECONFIGURATION_API_ROUTES = { - UPDATE_PATTERN: `${API_ROOT}/setup/preconfiguration`, RESET_PATTERN: `${INTERNAL_ROOT}/reset_preconfigured_agent_policies`, RESET_ONE_PATTERN: `${INTERNAL_ROOT}/reset_preconfigured_agent_policies/{agentPolicyId}`, }; diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 37b4e4800557f..db8470ed48fd5 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -59,76 +59,6 @@ ] } }, - "/setup/preconfiguration": { - "put": { - "summary": "Preconfiguration", - "operationId": "put-preconfiguration", - "tags": [], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "agentPolicies": { - "type": "object", - "items": {} - }, - "packages": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "version": { - "type": "string" - } - } - } - } - } - } - } - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "body": { - "type": "object", - "properties": { - "agentPolicies": { - "$ref": "#/components/schemas/preconfigured_agent_policies" - }, - "packages": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "name", - "version" - ] - } - } - } - } - } - } - } - } - } - }, "/settings": { "get": { "summary": "Settings", @@ -3029,35 +2959,7 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/simplified_package_policy" - }, - { - "deprecated": true, - "allOf": [ - { - "$ref": "#/components/schemas/new_package_policy" - }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - } - } - }, - { - "type": "object", - "properties": { - "force": { - "type": "boolean" - } - } - } - ] - } - ] + "$ref": "#/components/schemas/package_policy_request" } } } @@ -3346,7 +3248,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/update_package_policy" + "$ref": "#/components/schemas/package_policy_request" } } } @@ -3357,25 +3259,18 @@ "content": { "application/json": { "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/simplified_package_policy" + "type": "object", + "properties": { + "item": { + "$ref": "#/components/schemas/package_policy" }, - { - "type": "object", - "properties": { - "item": { - "$ref": "#/components/schemas/package_policy" - }, - "sucess": { - "type": "boolean" - } - }, - "required": [ - "item", - "sucess" - ] + "sucess": { + "type": "boolean" } + }, + "required": [ + "item", + "sucess" ] } } @@ -4496,141 +4391,6 @@ "nonFatalErrors" ] }, - "preconfigured_agent_policies": { - "title": "Preconfigured agent policies", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "is_managed": { - "type": "string" - }, - "unenroll_timeout": { - "type": "number" - }, - "monitoring_enabled": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "logs", - "metrics" - ] - } - }, - "namespace": { - "type": "string" - }, - "id": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - } - ] - }, - "is_default": { - "type": "boolean", - "deprecated": true - }, - "is_default_fleet_server": { - "type": "boolean", - "deprecated": true - }, - "has_fleet_server": { - "type": "boolean" - }, - "data_output_id": { - "type": "string" - }, - "monitoring_output_id": { - "type": "string" - }, - "package_policies": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - } - ] - }, - "name": { - "type": "string" - }, - "package": { - "type": "object", - "properties": { - "name": { - "type": "string" - } - } - }, - "description": { - "type": "string" - }, - "namespace": { - "type": "string" - }, - "inputs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "keep_enabled": { - "type": "boolean" - }, - "vars": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "value": { - "type": "string" - }, - "frozen": { - "type": "boolean" - } - } - } - }, - "required": [ - "type" - ] - } - } - } - } - } - }, - "required": [ - "name", - "namespace", - "package_policies" - ] - }, "settings": { "title": "Settings", "type": "object", @@ -5729,8 +5489,8 @@ "created_at" ] }, - "simplified_package_policy": { - "title": "Simplified Package Policy", + "package_policy_request": { + "title": "Package Policy Request", "type": "object", "properties": { "id": { @@ -5739,30 +5499,36 @@ }, "name": { "type": "string", - "description": "Package policy name (should be unique)" + "description": "Package policy name (should be unique)", + "example": "nginx-123" }, "description": { "type": "string", - "description": "Package policy description" + "description": "Package policy description", + "example": "my description" }, "namespace": { "type": "string", - "description": "namespace by default \"default\"" + "description": "namespace by default \"default\"", + "example": "default" }, "policy_id": { "type": "string", - "description": "Agent policy ID where that package policy will be added" + "description": "Agent policy ID to which the package policy will be added", + "example": "agent-policy-id" }, "package": { "type": "object", "properties": { "name": { "type": "string", - "description": "Package name" + "description": "Package name", + "example": "nginx" }, "version": { "type": "string", - "description": "Package version" + "description": "Package version", + "example": "1.6.0" } }, "required": [ @@ -5777,6 +5543,26 @@ "inputs": { "type": "object", "description": "Package policy inputs (see integration documentation to know what inputs are available)", + "example": { + "nginx-logfile": { + "enabled": true, + "streams": { + "nginx.access": { + "enabled": true, + "vars": { + "paths": [ + "/var/log/nginx/access.log*" + ], + "tags": [ + "nginx-access" + ], + "preserve_original_event": false, + "ignore_older": "72h" + } + } + } + } + }, "additionalProperties": { "type": "object", "properties": { @@ -5975,99 +5761,6 @@ } } }, - "update_package_policy": { - "title": "Update package policy", - "type": "object", - "description": "", - "properties": { - "version": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "package": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "version": { - "type": "string" - }, - "title": { - "type": "string" - } - }, - "required": [ - "name", - "title", - "version" - ] - }, - "namespace": { - "type": "string" - }, - "output_id": { - "type": "string", - "description": "Not supported output can be set at the agent policy level only", - "deprecated": true - }, - "inputs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "processors": { - "type": "array", - "items": { - "type": "string" - } - }, - "streams": { - "type": "array", - "items": {} - }, - "config": { - "type": "object" - }, - "vars": { - "type": "object" - } - }, - "required": [ - "type", - "enabled", - "streams" - ] - } - }, - "policy_id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "force": { - "type": "boolean" - } - }, - "required": [ - "name", - "namespace", - "policy_id", - "enabled" - ] - }, "output": { "title": "Output", "type": "object", diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 95fcd9513b467..f95d51ceb00b5 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -36,50 +36,6 @@ paths: operationId: setup parameters: - $ref: '#/components/parameters/kbn_xsrf' - /setup/preconfiguration: - put: - summary: Preconfiguration - operationId: put-preconfiguration - tags: [] - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - agentPolicies: - type: object - items: {} - packages: - type: object - properties: - name: - type: string - version: - type: string - requestBody: - content: - application/json: - schema: - type: object - properties: - body: - type: object - properties: - agentPolicies: - $ref: '#/components/schemas/preconfigured_agent_policies' - packages: - type: object - properties: - name: - type: string - version: - type: string - required: - - name - - version /settings: get: summary: Settings @@ -1883,19 +1839,7 @@ paths: content: application/json: schema: - oneOf: - - $ref: '#/components/schemas/simplified_package_policy' - - deprecated: true - allOf: - - $ref: '#/components/schemas/new_package_policy' - - type: object - properties: - id: - type: string - - type: object - properties: - force: - type: boolean + $ref: '#/components/schemas/package_policy_request' parameters: - $ref: '#/components/parameters/kbn_xsrf' /package_policies/_bulk_get: @@ -2074,24 +2018,22 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/update_package_policy' + $ref: '#/components/schemas/package_policy_request' responses: '200': description: OK content: application/json: schema: - oneOf: - - $ref: '#/components/schemas/simplified_package_policy' - - type: object - properties: - item: - $ref: '#/components/schemas/package_policy' - sucess: - type: boolean - required: - - item - - sucess + type: object + properties: + item: + $ref: '#/components/schemas/package_policy' + sucess: + type: boolean + required: + - item + - sucess parameters: - $ref: '#/components/parameters/kbn_xsrf' delete: @@ -2797,91 +2739,6 @@ components: required: - isInitialized - nonFatalErrors - preconfigured_agent_policies: - title: Preconfigured agent policies - type: object - properties: - name: - type: string - description: - type: string - is_managed: - type: string - unenroll_timeout: - type: number - monitoring_enabled: - type: array - items: - type: string - enum: - - logs - - metrics - namespace: - type: string - id: - oneOf: - - type: string - - type: number - is_default: - type: boolean - deprecated: true - is_default_fleet_server: - type: boolean - deprecated: true - has_fleet_server: - type: boolean - data_output_id: - type: string - monitoring_output_id: - type: string - package_policies: - type: array - items: - type: object - properties: - id: - oneOf: - - type: string - - type: number - name: - type: string - package: - type: object - properties: - name: - type: string - description: - type: string - namespace: - type: string - inputs: - type: array - items: - type: object - properties: - type: - type: string - enabled: - type: boolean - keep_enabled: - type: boolean - vars: - type: object - properties: - name: - type: string - type: - type: string - value: - type: string - frozen: - type: boolean - required: - - type - required: - - name - - namespace - - package_policies settings: title: Settings type: object @@ -3644,8 +3501,8 @@ components: - api_key - active - created_at - simplified_package_policy: - title: Simplified Package Policy + package_policy_request: + title: Package Policy Request type: object properties: id: @@ -3654,24 +3511,30 @@ components: name: type: string description: Package policy name (should be unique) + example: nginx-123 description: type: string description: Package policy description + example: my description namespace: type: string description: namespace by default "default" + example: default policy_id: type: string description: Agent policy ID where that package policy will be added + example: agent-policy-id package: type: object properties: name: type: string description: Package name + example: nginx version: type: string description: Package version + example: 1.6.0 required: - name - version @@ -3685,6 +3548,19 @@ components: description: >- Package policy inputs (see integration documentation to know what inputs are available) + example: + nginx-logfile: + enabled: true + streams: + nginx.access: + enabled: true + vars: + paths: + - /var/log/nginx/access.log* + tags: + - nginx-access + preserve_original_event: false + ignore_older: 72h additionalProperties: type: object properties: @@ -3819,71 +3695,6 @@ components: type: array items: $ref: '#/components/schemas/full_agent_policy_input' - update_package_policy: - title: Update package policy - type: object - description: '' - properties: - version: - type: string - enabled: - type: boolean - package: - type: object - properties: - name: - type: string - version: - type: string - title: - type: string - required: - - name - - title - - version - namespace: - type: string - output_id: - type: string - description: Not supported output can be set at the agent policy level only - deprecated: true - inputs: - type: array - items: - type: object - properties: - type: - type: string - enabled: - type: boolean - processors: - type: array - items: - type: string - streams: - type: array - items: {} - config: - type: object - vars: - type: object - required: - - type - - enabled - - streams - policy_id: - type: string - name: - type: string - description: - type: string - force: - type: boolean - required: - - name - - namespace - - policy_id - - enabled output: title: Output type: object diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/simplified_package_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/package_policy_request.yaml similarity index 78% rename from x-pack/plugins/fleet/common/openapi/components/schemas/simplified_package_policy.yaml rename to x-pack/plugins/fleet/common/openapi/components/schemas/package_policy_request.yaml index 6ff537386f05b..4f4f5489d82a2 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/simplified_package_policy.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/package_policy_request.yaml @@ -1,4 +1,4 @@ -title: Simplified Package Policy +title: Package Policy Request type: object properties: id: @@ -7,24 +7,30 @@ properties: name: type: string description: Package policy name (should be unique) + example: nginx-123 description: type: string description: Package policy description + example: 'my description' namespace: type: string description: namespace by default "default" + example: 'default' policy_id: type: string description: Agent policy ID where that package policy will be added + example: 'agent-policy-id' package: type: object properties: name: type: string description: Package name + example: 'nginx' version: type: string description: Package version + example: '1.6.0' required: - name - version @@ -34,6 +40,19 @@ properties: inputs: type: object description: Package policy inputs (see integration documentation to know what inputs are available) + example: + nginx-logfile: + enabled: true + streams: + nginx.access: + enabled: true + vars: + paths: + - '/var/log/nginx/access.log*' + tags: + - nginx-access + preserve_original_event: false + ignore_older: 72h additionalProperties: type: object properties: diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/preconfiguration_error.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/preconfiguration_error.yaml deleted file mode 100644 index 147488d3e78d8..0000000000000 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/preconfiguration_error.yaml +++ /dev/null @@ -1,26 +0,0 @@ -title: Preconfiguration Error -type: object -properties: - package: - type: object - properties: - name: - type: string - version: - type: string - agentPolicy: - type: object - properties: - name: - type: string - error: - type: object - properties: - name: - type: string - message: - type: string - stack: - type: string - required: - - error diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/preconfigured_agent_policies.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/preconfigured_agent_policies.yaml deleted file mode 100644 index 84feaf8fdeb91..0000000000000 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/preconfigured_agent_policies.yaml +++ /dev/null @@ -1,84 +0,0 @@ -title: Preconfigured agent policies -type: object -properties: - name: - type: string - description: - type: string - is_managed: - type: string - unenroll_timeout: - type: number - monitoring_enabled: - type: array - items: - type: string - enum: - - 'logs' - - 'metrics' - namespace: - type: string - id: - oneOf: - - type: string - - type: number - is_default: - type: boolean - deprecated: true - is_default_fleet_server: - type: boolean - deprecated: true - has_fleet_server: - type: boolean - data_output_id: - type: string - monitoring_output_id: - type: string - package_policies: - type: array - items: - type: object - properties: - id: - oneOf: - - type: string - - type: number - name: - type: string - package: - type: object - properties: - name: - type: string - description: - type: string - namespace: - type: string - inputs: - type: array - items: - type: object - properties: - type: - type: string - enabled: - type: boolean - keep_enabled: - type: boolean - vars: - type: object - properties: - name: - type: string - type: - type: string - value: - type: string - frozen: - type: boolean - required: - - type -required: - - name - - namespace - - package_policies diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index db0a25a8e55b3..648f11b5ad706 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -16,8 +16,6 @@ paths: # plugin-wide endpoint(s) /setup: $ref: paths/setup.yaml - /setup/preconfiguration: - $ref: paths/setup_preconfiguration.yaml /settings: $ref: paths/settings.yaml # App endpoints diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml index 10e4fba447bc5..ac609d2118fa4 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies.yaml @@ -45,19 +45,6 @@ post: content: application/json: schema: - oneOf: - - $ref: ../components/schemas/simplified_package_policy.yaml - # Using inputs as an array is deprecated - - deprecated: true - allOf: - - $ref: ../components/schemas/new_package_policy.yaml - - type: object - properties: - id: - type: string - - type: object - properties: - force: - type: boolean + $ref: ../components/schemas/package_policy_request.yaml parameters: - $ref: ../components/headers/kbn_xsrf.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml b/x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml index bf3e0e0a6eeff..bb4a76f1cd3df 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/package_policies@{package_policy_id}.yaml @@ -27,24 +27,22 @@ put: content: application/json: schema: - $ref: ../components/schemas/update_package_policy.yaml + $ref: ../components/schemas/package_policy_request.yaml responses: '200': description: OK content: application/json: schema: - oneOf: - - $ref: ../components/schemas/simplified_package_policy.yaml - - type: object - properties: - item: - $ref: ../components/schemas/package_policy.yaml - sucess: - type: boolean - required: - - item - - sucess + type: object + properties: + item: + $ref: ../components/schemas/package_policy.yaml + sucess: + type: boolean + required: + - item + - sucess parameters: - $ref: ../components/headers/kbn_xsrf.yaml delete: diff --git a/x-pack/plugins/fleet/common/openapi/paths/setup_preconfiguration.yaml b/x-pack/plugins/fleet/common/openapi/paths/setup_preconfiguration.yaml deleted file mode 100644 index a01536b5634f6..0000000000000 --- a/x-pack/plugins/fleet/common/openapi/paths/setup_preconfiguration.yaml +++ /dev/null @@ -1,43 +0,0 @@ -put: - summary: Preconfiguration - operationId: put-preconfiguration - tags: [] - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - agentPolicies: - type: object - items: {} - packages: - type: object - properties: - name: - type: string - version: - type: string - requestBody: - content: - application/json: - schema: - type: object - properties: - body: - type: object - properties: - agentPolicies: - $ref: ../components/schemas/preconfigured_agent_policies.yaml - packages: - type: object - properties: - name: - type: string - version: - type: string - required: - - name - - version diff --git a/x-pack/plugins/fleet/common/services/__snapshots__/package_to_package_policy.test.ts.snap b/x-pack/plugins/fleet/common/services/__snapshots__/package_to_package_policy.test.ts.snap index 1edc3d850f775..3235ebbd9a8f9 100644 --- a/x-pack/plugins/fleet/common/services/__snapshots__/package_to_package_policy.test.ts.snap +++ b/x-pack/plugins/fleet/common/services/__snapshots__/package_to_package_policy.test.ts.snap @@ -15,7 +15,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "cost_explorer_config.group_by_dimension_keys": Object { "type": "text", @@ -54,7 +53,6 @@ Object { "type": "logs", }, "enabled": true, - "release": "beta", "vars": Object { "api_timeout": Object { "type": "text", @@ -87,7 +85,6 @@ Object { "type": "logs", }, "enabled": false, - "release": "beta", "vars": Object { "interval": Object { "type": "text", @@ -134,7 +131,6 @@ Object { "type": "logs", }, "enabled": true, - "release": "beta", "vars": Object { "api_timeout": Object { "type": "text", @@ -167,7 +163,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -214,7 +209,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -249,7 +243,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -284,7 +277,6 @@ Object { "type": "logs", }, "enabled": true, - "release": "beta", "vars": Object { "api_timeout": Object { "type": "text", @@ -317,7 +309,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -352,7 +343,6 @@ Object { "type": "logs", }, "enabled": true, - "release": "beta", "vars": Object { "api_timeout": Object { "type": "text", @@ -385,7 +375,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -420,7 +409,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -455,7 +443,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -484,7 +471,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -519,7 +505,6 @@ Object { "type": "logs", }, "enabled": true, - "release": "beta", "vars": Object { "api_timeout": Object { "type": "text", @@ -552,7 +537,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -574,7 +558,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -603,7 +586,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -638,7 +620,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -667,7 +648,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -696,7 +676,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", @@ -725,7 +704,6 @@ Object { "type": "logs", }, "enabled": true, - "release": "beta", "vars": Object { "api_timeout": Object { "type": "text", @@ -758,7 +736,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "beta", "vars": Object { "latency": Object { "type": "text", diff --git a/x-pack/plugins/fleet/common/services/__snapshots__/simplified_package_policy_helper.test.ts.snap b/x-pack/plugins/fleet/common/services/__snapshots__/simplified_package_policy_helper.test.ts.snap index 35c8b917853f9..200eed919407e 100644 --- a/x-pack/plugins/fleet/common/services/__snapshots__/simplified_package_policy_helper.test.ts.snap +++ b/x-pack/plugins/fleet/common/services/__snapshots__/simplified_package_policy_helper.test.ts.snap @@ -15,7 +15,6 @@ Object { "type": "logs", }, "enabled": true, - "release": "ga", "vars": Object { "ignore_older": Object { "type": "text", @@ -49,7 +48,6 @@ Object { "type": "logs", }, "enabled": true, - "release": "ga", "vars": Object { "ignore_older": Object { "type": "text", @@ -91,7 +89,6 @@ Object { "type": "logs", }, "enabled": false, - "release": "ga", "vars": Object { "interval": Object { "type": "text", @@ -124,7 +121,6 @@ Object { "type": "logs", }, "enabled": false, - "release": "ga", "vars": Object { "interval": Object { "type": "text", @@ -207,7 +203,6 @@ Object { "type": "metrics", }, "enabled": true, - "release": "ga", "vars": Object { "period": Object { "type": "text", diff --git a/x-pack/plugins/fleet/common/services/index.ts b/x-pack/plugins/fleet/common/services/index.ts index 0fd88833b4ea5..1d75aa34e9239 100644 --- a/x-pack/plugins/fleet/common/services/index.ts +++ b/x-pack/plugins/fleet/common/services/index.ts @@ -11,6 +11,7 @@ export { packageToPackagePolicyInputs, packageToPackagePolicy, getStreamsForInputType, + getRegistryStreamWithDataStreamForInputType, } from './package_to_package_policy'; export { fullAgentPolicyToYaml } from './full_agent_policy_to_yaml'; export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limited_package'; diff --git a/x-pack/plugins/fleet/common/services/package_to_package_policy.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.ts index 9caa0616738c3..50dd519bc00cd 100644 --- a/x-pack/plugins/fleet/common/services/package_to_package_policy.ts +++ b/x-pack/plugins/fleet/common/services/package_to_package_policy.ts @@ -15,6 +15,7 @@ import type { NewPackagePolicyInputStream, NewPackagePolicy, PackagePolicyConfigRecordEntry, + RegistryStreamWithDataStream, } from '../types'; import { doesPackageHaveIntegrations } from '.'; @@ -24,7 +25,7 @@ import { isIntegrationPolicyTemplate, } from './policy_template'; -type PackagePolicyStream = RegistryStream & { release?: 'beta' | 'experimental' | 'ga' } & { +type PackagePolicyStream = RegistryStream & { data_stream: { type: string; dataset: string }; }; @@ -48,7 +49,33 @@ export const getStreamsForInputType = ( type: dataStream.type, dataset: dataStream.dataset, }, - release: dataStream.release, + }); + } + }); + }); + + return streams; +}; + +export const getRegistryStreamWithDataStreamForInputType = ( + inputType: string, + packageInfo: PackageInfo, + dataStreamPaths: string[] = [] +): RegistryStreamWithDataStream[] => { + const streams: RegistryStreamWithDataStream[] = []; + const dataStreams = getNormalizedDataStreams(packageInfo); + const dataStreamsToSearch = dataStreamPaths.length + ? dataStreams.filter((dataStream) => dataStreamPaths.includes(dataStream.path)) + : dataStreams; + + dataStreamsToSearch.forEach((dataStream) => { + (dataStream.streams || []).forEach((stream) => { + if (stream.input === inputType) { + streams.push({ + ...stream, + data_stream: { + ...dataStream, + }, }); } }); @@ -113,7 +140,6 @@ export const packageToPackagePolicyInputs = ( const stream: NewPackagePolicyInputStream = { enabled: packageStream.enabled === false ? false : true, data_stream: packageStream.data_stream, - release: packageStream.release, }; if (packageStream.vars && packageStream.vars.length) { stream.vars = packageStream.vars.reduce(varsReducer, {}); diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index f1b1ca75c3027..d8d0dc250edc1 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -244,6 +244,8 @@ export interface RegistryStream { [RegistryStreamKeys.template_path]: string; } +export type RegistryStreamWithDataStream = RegistryStream & { data_stream: RegistryDataStream }; + export type RequirementVersion = string; export type RequirementVersionRange = string; export interface ServiceRequirements { diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts index 0a951c0de8fce..5bd10687328c3 100644 --- a/x-pack/plugins/fleet/common/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts @@ -34,6 +34,7 @@ export interface NewPackagePolicyInputStream { indices?: string[]; }; index_mode?: string; + source_mode?: string; }; }; release?: RegistryRelease; diff --git a/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts b/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts index 9cdbe6b3a97cc..cce1269585cbb 100644 --- a/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/integrations_real.cy.ts @@ -174,10 +174,10 @@ describe('Add Integration - Real API', () => { setupIntegrations(); cy.getBySel(getIntegrationCategories('aws')).click(); cy.getBySel(INTEGRATIONS_SEARCHBAR.BADGE).contains('AWS').should('exist'); - cy.getBySel(INTEGRATION_LIST).find('.euiCard').should('have.length', 29); + cy.getBySel(INTEGRATION_LIST).find('.euiCard').should('have.length.greaterThan', 29); cy.getBySel(INTEGRATIONS_SEARCHBAR.INPUT).clear().type('Cloud'); - cy.getBySel(INTEGRATION_LIST).find('.euiCard').should('have.length', 3); + cy.getBySel(INTEGRATION_LIST).find('.euiCard').should('have.length.greaterThan', 3); cy.getBySel(INTEGRATIONS_SEARCHBAR.REMOVE_BADGE_BUTTON).click(); cy.getBySel(INTEGRATIONS_SEARCHBAR.BADGE).should('not.exist'); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/dataset_combo.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/dataset_combo.tsx new file mode 100644 index 0000000000000..4588ebc4912b3 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/dataset_combo.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; +import { EuiComboBox } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const DatasetComboBox: React.FC<{ + value: any; + onChange: (newValue: any) => void; + datasets: string[]; +}> = ({ value, onChange, datasets }) => { + const datasetOptions = datasets.map((dataset: string) => ({ label: dataset })) ?? []; + const defaultOption = 'generic'; + const [selectedOptions, setSelectedOptions] = useState>([ + { + label: value ?? defaultOption, + }, + ]); + + useEffect(() => { + if (!value) onChange(defaultOption); + }, [value, defaultOption, onChange]); + + const onDatasetChange = (newSelectedOptions: Array<{ label: string }>) => { + setSelectedOptions(newSelectedOptions); + onChange(newSelectedOptions[0]?.label); + }; + + const onCreateOption = (searchValue: string = '') => { + const normalizedSearchValue = searchValue.trim().toLowerCase(); + if (!normalizedSearchValue) { + return; + } + const newOption = { + label: searchValue, + }; + setSelectedOptions([newOption]); + onChange(searchValue); + }; + + return ( + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.test.tsx new file mode 100644 index 0000000000000..e552288d673d8 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.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 { createFleetTestRendererMock } from '../../../../../../../../mock'; + +import type { RegistryDataStream } from '../../../../../../../../../common/types'; + +import { ExperimentDatastreamSettings } from './experimental_datastream_settings'; + +jest.mock('../../../../../../../../hooks', () => { + return { + ...jest.requireActual('../../../../../../../../hooks'), + FleetStatusProvider: (props: any) => { + return props.children; + }, + useFleetStatus: jest.fn().mockReturnValue({ isReady: true } as any), + sendGetStatus: jest + .fn() + .mockResolvedValue({ data: { isReady: true, missing_requirements: [] } }), + }; +}); + +describe('ExperimentDatastreamSettings', () => { + describe('Synthetic source', () => { + it('should be enabled an not checked by default', () => { + const mockSetNewExperimentalDataFeatures = jest.fn(); + const res = createFleetTestRendererMock().render( + + ); + + const syntheticSourceSwitch = res.getByTestId( + 'packagePolicyEditor.syntheticSourceExperimentalFeature.switch' + ); + expect(syntheticSourceSwitch).not.toBeChecked(); + expect(syntheticSourceSwitch).toBeEnabled(); + expect(mockSetNewExperimentalDataFeatures).not.toBeCalled(); + }); + + it('should be checked if the regitry datastream define source_mode synthetic', () => { + const mockSetNewExperimentalDataFeatures = jest.fn(); + const res = createFleetTestRendererMock().render( + + ); + + const syntheticSourceSwitch = res.getByTestId( + 'packagePolicyEditor.syntheticSourceExperimentalFeature.switch' + ); + expect(syntheticSourceSwitch).toBeChecked(); + expect(syntheticSourceSwitch).toBeEnabled(); + expect(mockSetNewExperimentalDataFeatures).not.toBeCalled(); + }); + + it('should be not checked and disabled if the regitry datastream define source_mode synthetic and the user disabled it', () => { + const mockSetNewExperimentalDataFeatures = jest.fn(); + const res = createFleetTestRendererMock().render( + + ); + + const syntheticSourceSwitch = res.getByTestId( + 'packagePolicyEditor.syntheticSourceExperimentalFeature.switch' + ); + expect(syntheticSourceSwitch).not.toBeChecked(); + expect(syntheticSourceSwitch).toBeEnabled(); + expect(mockSetNewExperimentalDataFeatures).not.toBeCalled(); + }); + + it('should not be checked and not enabled if the regitry datastream define source_mode default', () => { + const mockSetNewExperimentalDataFeatures = jest.fn(); + const res = createFleetTestRendererMock().render( + + ); + + const syntheticSourceSwitch = res.getByTestId( + 'packagePolicyEditor.syntheticSourceExperimentalFeature.switch' + ); + expect(syntheticSourceSwitch).not.toBeChecked(); + expect(syntheticSourceSwitch).not.toBeEnabled(); + expect(mockSetNewExperimentalDataFeatures).not.toBeCalled(); + }); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.tsx new file mode 100644 index 0000000000000..c91e5bf767666 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.tsx @@ -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 React from 'react'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSwitch, + EuiText, + EuiSpacer, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; + +import type { + ExperimentalDataStreamFeature, + RegistryDataStream, +} from '../../../../../../../../../common/types'; +import { getRegistryDataStreamAssetBaseName } from '../../../../../../../../../common/services'; +import type { ExperimentalIndexingFeature } from '../../../../../../../../../common/types/models/epm'; + +interface Props { + registryDataStream: RegistryDataStream; + experimentalDataFeatures?: ExperimentalDataStreamFeature[]; + setNewExperimentalDataFeatures: ( + experimentalDataFeatures: ExperimentalDataStreamFeature[] + ) => void; +} + +function getExperimentalFeatureValue( + feature: ExperimentalIndexingFeature, + experimentalDataFeatures: ExperimentalDataStreamFeature[], + registryDataStream: RegistryDataStream +) { + return experimentalDataFeatures?.find( + ({ data_stream: dataStream, features }) => + dataStream === getRegistryDataStreamAssetBaseName(registryDataStream) && + typeof features[feature] !== 'undefined' + )?.features?.[feature]; +} + +export const ExperimentDatastreamSettings: React.FunctionComponent = ({ + registryDataStream, + experimentalDataFeatures, + setNewExperimentalDataFeatures, +}) => { + const isSyntheticSourceEditable = registryDataStream.elasticsearch?.source_mode !== 'default'; + + const syntheticSourceExperimentalValue = getExperimentalFeatureValue( + 'synthetic_source', + experimentalDataFeatures ?? [], + registryDataStream + ); + const isSyntheticSourceEnabledByDefault = + registryDataStream.elasticsearch?.source_mode === 'synthetic'; + + const newExperimentalIndexingFeature = { + synthetic_source: + typeof syntheticSourceExperimentalValue !== 'undefined' + ? syntheticSourceExperimentalValue + : isSyntheticSourceEnabledByDefault, + tsdb: + getExperimentalFeatureValue('tsdb', experimentalDataFeatures ?? [], registryDataStream) ?? + false, + }; + + const onIndexingSettingChange = ( + features: Partial> + ) => { + const newExperimentalDataStreamFeatures = [...(experimentalDataFeatures ?? [])]; + + const dataStream = getRegistryDataStreamAssetBaseName(registryDataStream); + + const existingSettingRecord = newExperimentalDataStreamFeatures.find( + (x) => x.data_stream === dataStream + ); + + if (existingSettingRecord) { + existingSettingRecord.features = { + ...existingSettingRecord.features, + ...features, + }; + } else { + newExperimentalDataStreamFeatures.push({ + data_stream: dataStream, + features: { ...newExperimentalIndexingFeature, ...features }, + }); + } + + setNewExperimentalDataFeatures(newExperimentalDataStreamFeatures); + }; + + return ( + + + + +
+ +
+
+
+ + + + + + ), + }} + /> + + + + + + } + onChange={(e) => { + onIndexingSettingChange({ + synthetic_source: e.target.checked, + }); + }} + /> + + + + } + > + + } + onChange={(e) => { + onIndexingSettingChange({ + tsdb: e.target.checked, + }); + }} + /> + + +
+
+ ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.test.ts new file mode 100644 index 0000000000000..521c5e08dc2c1 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { orderDatasets } from './order_datasets'; + +describe('orderDatasets', () => { + it('should move datasets up that match name', () => { + const datasets = orderDatasets( + ['system.memory', 'elastic_agent', 'elastic_agent.filebeat', 'system.cpu'], + 'elastic_agent' + ); + + expect(datasets).toEqual([ + 'elastic_agent', + 'elastic_agent.filebeat', + 'system.cpu', + 'system.memory', + ]); + }); + + it('should order alphabetically if name does not match', () => { + const datasets = orderDatasets(['system.memory', 'elastic_agent'], 'nginx'); + + expect(datasets).toEqual(['elastic_agent', 'system.memory']); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.ts new file mode 100644 index 0000000000000..8262af7064142 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/order_datasets.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { partition } from 'lodash'; + +export function orderDatasets(datasetList: string[], name: string): string[] { + const [relevantDatasets, otherDatasets] = partition(datasetList.sort(), (record) => + record.startsWith(name) + ); + const datasets = relevantDatasets.concat(otherDatasets); + return datasets; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx index 7bcda09d08d8f..8ea01f3100b75 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_panel.tsx @@ -27,6 +27,7 @@ import type { PackagePolicyInputStream, RegistryInput, RegistryStream, + RegistryStreamWithDataStream, } from '../../../../../../types'; import type { PackagePolicyInputValidationResults } from '../../../services'; import { hasInvalidButRequiredVar, countValidationErrors } from '../../../services'; @@ -74,7 +75,7 @@ export const PackagePolicyInputPanel: React.FunctionComponent<{ packageInput: RegistryInput; packageInfo: PackageInfo; packagePolicy: NewPackagePolicy; - packageInputStreams: Array; + packageInputStreams: RegistryStreamWithDataStream[]; packagePolicyInput: NewPackagePolicyInput; updatePackagePolicy: (updatedPackagePolicy: Partial) => void; updatePackagePolicyInput: (updatedInput: Partial) => void; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx index 184deb98abd63..4bfdff96a9d50 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx @@ -8,6 +8,7 @@ import React, { useState, Fragment, memo, useMemo, useEffect, useRef, useCallback } from 'react'; import ReactMarkdown from 'react-markdown'; import styled from 'styled-components'; +import { uniq } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFlexGrid, @@ -17,20 +18,19 @@ import { EuiText, EuiSpacer, EuiButtonEmpty, - EuiTitle, - EuiToolTip, } from '@elastic/eui'; import { useRouteMatch } from 'react-router-dom'; +import { useGetDataStreams } from '../../../../../../../../hooks'; + import { mapPackageReleaseToIntegrationCardRelease } from '../../../../../../../../services/package_prerelease'; +import type { ExperimentalDataStreamFeature } from '../../../../../../../../../common/types/models/epm'; -import { getRegistryDataStreamAssetBaseName } from '../../../../../../../../../common/services'; -import type { ExperimentalIndexingFeature } from '../../../../../../../../../common/types/models/epm'; import type { NewPackagePolicy, NewPackagePolicyInputStream, PackageInfo, - RegistryStream, + RegistryStreamWithDataStream, RegistryVarsEntry, } from '../../../../../../types'; import { InlineReleaseBadge } from '../../../../../../components'; @@ -39,8 +39,10 @@ import { isAdvancedVar, validationHasErrors } from '../../../services'; import { PackagePolicyEditorDatastreamPipelines } from '../../datastream_pipelines'; import { PackagePolicyEditorDatastreamMappings } from '../../datastream_mappings'; +import { ExperimentDatastreamSettings } from './experimental_datastream_settings'; import { PackagePolicyInputVarField } from './package_policy_input_var_field'; import { useDataStreamId } from './hooks'; +import { orderDatasets } from './order_datasets'; const ScrollAnchor = styled.div` display: none; @@ -49,7 +51,7 @@ const ScrollAnchor = styled.div` interface Props { packagePolicy: NewPackagePolicy; - packageInputStream: RegistryStream & { data_stream: { dataset: string; type: string } }; + packageInputStream: RegistryStreamWithDataStream; packageInfo: PackageInfo; packagePolicyInputStream: NewPackagePolicyInputStream; updatePackagePolicy: (updatedPackagePolicy: Partial) => void; @@ -119,61 +121,26 @@ export const PackagePolicyInputStreamConfig = memo( [advancedVars, inputStreamValidationResults?.vars] ); - const isFeatureEnabled = useCallback( - (feature: ExperimentalIndexingFeature) => - packagePolicy.package?.experimental_data_stream_features?.some( - ({ data_stream: dataStream, features }) => - dataStream === - getRegistryDataStreamAssetBaseName(packagePolicyInputStream.data_stream) && - features[feature] - ) ?? false, - [ - packagePolicy.package?.experimental_data_stream_features, - packagePolicyInputStream.data_stream, - ] - ); - - const newExperimentalIndexingFeature = { - synthetic_source: isFeatureEnabled('synthetic_source'), - tsdb: isFeatureEnabled('tsdb'), - }; - - const onIndexingSettingChange = ( - features: Partial> - ) => { - if (!packagePolicy.package) { - return; - } - - const newExperimentalDataStreamFeatures = [ - ...(packagePolicy.package.experimental_data_stream_features ?? []), - ]; + const setNewExperimentalDataFeatures = useCallback( + (newFeatures: ExperimentalDataStreamFeature[]) => { + if (!packagePolicy.package) { + return; + } - const dataStream = getRegistryDataStreamAssetBaseName(packagePolicyInputStream.data_stream); - - const existingSettingRecord = newExperimentalDataStreamFeatures.find( - (x) => x.data_stream === dataStream - ); - - if (existingSettingRecord) { - existingSettingRecord.features = { - ...existingSettingRecord.features, - ...features, - }; - } else { - newExperimentalDataStreamFeatures.push({ - data_stream: dataStream, - features: { ...newExperimentalIndexingFeature, ...features }, + updatePackagePolicy({ + package: { + ...packagePolicy.package, + experimental_data_stream_features: newFeatures, + }, }); - } + }, + [updatePackagePolicy, packagePolicy] + ); - updatePackagePolicy({ - package: { - ...packagePolicy.package, - experimental_data_stream_features: newExperimentalDataStreamFeatures, - }, - }); - }; + const { data: dataStreamsData } = useGetDataStreams(); + const datasetList = + uniq(dataStreamsData?.data_streams.map((dataStream) => dataStream.dataset)) ?? []; + const datasets = orderDatasets(datasetList, packageInfo.name); return ( <> @@ -203,11 +170,12 @@ export const PackagePolicyInputStreamConfig = memo( /> )} - {packagePolicyInputStream.release && packagePolicyInputStream.release !== 'ga' ? ( + {packageInputStream.data_stream.release && + packageInputStream.data_stream.release !== 'ga' ? ( @@ -252,6 +220,8 @@ export const PackagePolicyInputStreamConfig = memo( }} errors={inputStreamValidationResults?.vars![varName]} forceShowErrors={forceShowErrors} + packageType={packageInfo.type} + datasets={datasets} /> ); @@ -311,6 +281,8 @@ export const PackagePolicyInputStreamConfig = memo( }} errors={inputStreamValidationResults?.vars![varName]} forceShowErrors={forceShowErrors} + packageType={packageInfo.type} + datasets={datasets} /> ); @@ -333,81 +305,13 @@ export const PackagePolicyInputStreamConfig = memo( )} {/* Experimental index/datastream settings e.g. synthetic source */} - - - - -
- -
-
-
- - - - - - ), - }} - /> - - - - - - } - onChange={(e) => { - onIndexingSettingChange({ - synthetic_source: e.target.checked, - }); - }} - /> - - - - } - > - - } - onChange={(e) => { - onIndexingSettingChange({ - tsdb: e.target.checked, - }); - }} - /> - - -
-
+ ) : null} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx index 812d5fc2233de..e314fb2c79ca6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_var_field.tsx @@ -25,6 +25,7 @@ import { CodeEditor } from '@kbn/kibana-react-plugin/public'; import type { RegistryVarsEntry } from '../../../../../../types'; import { MultiTextInput } from './multi_text_input'; +import { DatasetComboBox } from './dataset_combo'; const FixedHeightDiv = styled.div` height: 300px; @@ -37,127 +38,144 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{ errors?: string[] | null; forceShowErrors?: boolean; frozen?: boolean; -}> = memo(({ varDef, value, onChange, errors: varErrors, forceShowErrors, frozen }) => { - const [isDirty, setIsDirty] = useState(false); - const { multi, required, type, title, name, description } = varDef; - const isInvalid = (isDirty || forceShowErrors) && !!varErrors; - const errors = isInvalid ? varErrors : null; - const fieldLabel = title || name; + packageType?: string; + datasets?: string[]; +}> = memo( + ({ + varDef, + value, + onChange, + errors: varErrors, + forceShowErrors, + frozen, + packageType, + datasets = [], + }) => { + const [isDirty, setIsDirty] = useState(false); + const { multi, required, type, title, name, description } = varDef; + const isInvalid = (isDirty || forceShowErrors) && !!varErrors; + const errors = isInvalid ? varErrors : null; + const fieldLabel = title || name; - const field = useMemo(() => { - if (multi) { - return ( - setIsDirty(true)} - isDisabled={frozen} - /> - ); - } - switch (type) { - case 'textarea': + const field = useMemo(() => { + if (multi) { return ( - onChange(e.target.value)} + setIsDirty(true)} - disabled={frozen} - resize="vertical" + isDisabled={frozen} /> ); - case 'yaml': - return frozen ? ( - -
{value}
-
- ) : ( - - - - ); - case 'bool': - return ( - onChange(e.target.checked)} - onBlur={() => setIsDirty(true)} - disabled={frozen} - /> - ); - case 'password': - return ( - onChange(e.target.value)} - onBlur={() => setIsDirty(true)} - disabled={frozen} - /> - ); - default: - return ( - onChange(e.target.value)} - onBlur={() => setIsDirty(true)} - disabled={frozen} - /> - ); - } - }, [isInvalid, multi, onChange, type, value, fieldLabel, frozen]); - - // Boolean cannot be optional by default set to false - const isOptional = useMemo(() => type !== 'bool' && !required, [required, type]); + } - return ( - - ; + } + switch (type) { + case 'textarea': + return ( + onChange(e.target.value)} + onBlur={() => setIsDirty(true)} + disabled={frozen} + resize="vertical" /> - - ) : null + ); + case 'yaml': + return frozen ? ( + +
{value}
+
+ ) : ( + + + + ); + case 'bool': + return ( + onChange(e.target.checked)} + onBlur={() => setIsDirty(true)} + disabled={frozen} + /> + ); + case 'password': + return ( + onChange(e.target.value)} + onBlur={() => setIsDirty(true)} + disabled={frozen} + /> + ); + default: + return ( + onChange(e.target.value)} + onBlur={() => setIsDirty(true)} + disabled={frozen} + /> + ); } - helpText={description && } - > - {field} -
- ); -}); + }, [isInvalid, multi, onChange, type, value, fieldLabel, frozen, datasets, name, packageType]); + + // Boolean cannot be optional by default set to false + const isOptional = useMemo(() => type !== 'bool' && !required, [required, type]); + + return ( + + + + ) : null + } + helpText={description && } + > + {field} + + ); + } +); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx index de525ad235119..01968fd601996 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_configure_package.tsx @@ -18,11 +18,12 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { getNormalizedInputs, isIntegrationPolicyTemplate, + getRegistryStreamWithDataStreamForInputType, } from '../../../../../../../../common/services'; import type { PackageInfo, NewPackagePolicy, NewPackagePolicyInput } from '../../../../../types'; import { Loading } from '../../../../../components'; -import { getStreamsForInputType, doesPackageHaveIntegrations } from '../../../../../services'; +import { doesPackageHaveIntegrations } from '../../../../../services'; import type { PackagePolicyValidationResults } from '../../services'; @@ -69,7 +70,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ input.type === packageInput.type && (hasIntegrations ? input.policy_template === policyTemplate.name : true) ); - const packageInputStreams = getStreamsForInputType( + const packageInputStreams = getRegistryStreamWithDataStreamForInputType( packageInput.type, packageInfo, hasIntegrations && isIntegrationPolicyTemplate(policyTemplate) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx index c359bc9679e44..58cefafc27469 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx @@ -125,7 +125,6 @@ describe('when on the package policy create page', () => { name: 'nginx', title: 'Nginx', version: '1.3.0', - release: 'ga', description: 'Collect logs and metrics from Nginx HTTP servers with Elastic Agent.', policy_templates: [ { @@ -147,7 +146,6 @@ describe('when on the package policy create page', () => { type: 'logs', dataset: 'nginx.access', title: 'Nginx access logs', - release: 'experimental', ingest_pipeline: 'default', streams: [ { @@ -239,7 +237,6 @@ describe('when on the package policy create page', () => { dataset: 'nginx.access', type: 'logs', }, - release: 'experimental', enabled: true, vars: { paths: { @@ -537,7 +534,6 @@ describe('when on the package policy create page', () => { streams: [ { ...newPackagePolicy.inputs[0].streams[0], - release: 'experimental', vars: { paths: { type: 'text', diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx index 74922314a8c48..4a3fc126cce59 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx @@ -8,6 +8,7 @@ import type { ReactNode, FunctionComponent } from 'react'; import { useMemo } from 'react'; import React, { useCallback, useState, useRef, useEffect } from 'react'; +import { css } from '@emotion/react'; import { EuiFlexGrid, @@ -257,7 +258,17 @@ function GridColumn({ {list.length ? ( list.map((item) => { return ( - + .euiPopover, + & > .euiPopover > .euiPopover__anchor, + & > .euiPopover > .euiPopover__anchor > .euiCard { + height: 100%; + } + `} + > ); diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index 189f71138434c..1f986e8e27b12 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -102,6 +102,7 @@ export type { RegistryVarsEntry, RegistryInput, RegistryStream, + RegistryStreamWithDataStream, RegistryPolicyTemplate, PackageList, PackageListItem, diff --git a/x-pack/plugins/fleet/scripts/get_all_packages/get_all_packages.ts b/x-pack/plugins/fleet/scripts/get_all_packages/get_all_packages.ts index 481ed9d615400..218ddb751dbf2 100644 --- a/x-pack/plugins/fleet/scripts/get_all_packages/get_all_packages.ts +++ b/x-pack/plugins/fleet/scripts/get_all_packages/get_all_packages.ts @@ -14,7 +14,7 @@ import yargs from 'yargs/yargs'; import type { PackageInfo } from '../../common'; -const REGISTRY_URL = 'https://epr-snapshot.elastic.co'; +const REGISTRY_URL = 'https://epr.elastic.co'; const KIBANA_URL = 'http://localhost:5601'; const KIBANA_USERNAME = 'elastic'; const KIBANA_PASSWORD = 'changeme'; diff --git a/x-pack/plugins/fleet/scripts/install_all_packages/install_all_packages.ts b/x-pack/plugins/fleet/scripts/install_all_packages/install_all_packages.ts index 4215a460a29cb..2b8178c8f2247 100644 --- a/x-pack/plugins/fleet/scripts/install_all_packages/install_all_packages.ts +++ b/x-pack/plugins/fleet/scripts/install_all_packages/install_all_packages.ts @@ -9,7 +9,7 @@ import fetch from 'node-fetch'; import { kibanaPackageJson } from '@kbn/utils'; import { ToolingLog } from '@kbn/tooling-log'; -const REGISTRY_URL = 'https://epr-snapshot.elastic.co'; +const REGISTRY_URL = 'https://epr.elastic.co'; const KIBANA_URL = 'http://localhost:5601'; const KIBANA_USERNAME = 'elastic'; const KIBANA_PASSWORD = 'changeme'; diff --git a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts index 7861e7ac606a4..a9bf772602cf8 100644 --- a/x-pack/plugins/fleet/server/collectors/agent_collectors.ts +++ b/x-pack/plugins/fleet/server/collectors/agent_collectors.ts @@ -49,7 +49,10 @@ export const getAgentUsage = async ( }; export interface AgentData { - agent_versions: string[]; + agents_per_version: Array<{ + version: string; + count: number; + }>; agent_checkin_status: { error: number; degraded: number; @@ -58,9 +61,9 @@ export interface AgentData { } const DEFAULT_AGENT_DATA = { - agent_versions: [], agent_checkin_status: { error: 0, degraded: 0 }, agents_per_policy: [], + agents_per_version: [], }; export const getAgentData = async ( @@ -105,8 +108,8 @@ export const getAgentData = async ( }, { signal: abortController.signal } ); - const versions = ((response?.aggregations?.versions as any).buckets ?? []).map( - (bucket: any) => bucket.key + const agentsPerVersion = ((response?.aggregations?.versions as any).buckets ?? []).map( + (bucket: any) => ({ version: bucket.key, count: bucket.doc_count }) ); const statuses = transformLastCheckinStatusBuckets(response); @@ -115,9 +118,9 @@ export const getAgentData = async ( ); return { - agent_versions: versions, agent_checkin_status: statuses, agents_per_policy: agentsPerPolicy, + agents_per_version: agentsPerVersion, }; } catch (error) { if (error.statusCode === 404) { diff --git a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts index 474c37025f65a..de518f27562a7 100644 --- a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts @@ -265,7 +265,10 @@ describe('fleet usage telemetry', () => { num_host_urls: 0, }, packages: [], - agent_versions: ['8.5.1', '8.6.0'], + agents_per_version: [ + { version: '8.5.1', count: 1 }, + { version: '8.6.0', count: 1 }, + ], agent_checkin_status: { error: 1, degraded: 1 }, agents_per_policy: [2], fleet_server_config: { diff --git a/x-pack/plugins/fleet/server/routes/preconfiguration/handler.ts b/x-pack/plugins/fleet/server/routes/preconfiguration/handler.ts index 64f98746a19f9..7b2956b7ec460 100644 --- a/x-pack/plugins/fleet/server/routes/preconfiguration/handler.ts +++ b/x-pack/plugins/fleet/server/routes/preconfiguration/handler.ts @@ -7,51 +7,11 @@ import type { TypeOf } from '@kbn/config-schema'; -import type { PreconfiguredAgentPolicy } from '../../../common/types'; - import type { FleetRequestHandler } from '../../types'; -import type { - PutPreconfigurationSchema, - PostResetOnePreconfiguredAgentPoliciesSchema, -} from '../../types'; +import type { PostResetOnePreconfiguredAgentPoliciesSchema } from '../../types'; import { defaultFleetErrorHandler } from '../../errors'; -import { - ensurePreconfiguredPackagesAndPolicies, - outputService, - downloadSourceService, -} from '../../services'; import { resetPreconfiguredAgentPolicies } from '../../services/preconfiguration/reset_agent_policies'; -export const updatePreconfigurationHandler: FleetRequestHandler< - undefined, - undefined, - TypeOf -> = async (context, request, response) => { - const coreContext = await context.core; - const fleetContext = await context.fleet; - const soClient = coreContext.savedObjects.client; - const esClient = coreContext.elasticsearch.client.asInternalUser; - const defaultOutput = await outputService.ensureDefaultOutput(soClient); - const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient); - const spaceId = fleetContext.spaceId; - const { agentPolicies, packages } = request.body; - - try { - const body = await ensurePreconfiguredPackagesAndPolicies( - soClient, - esClient, - (agentPolicies as PreconfiguredAgentPolicy[]) ?? [], - packages ?? [], - defaultOutput, - defaultDownloadSource, - spaceId - ); - return response.ok({ body }); - } catch (error) { - return defaultFleetErrorHandler({ error, response }); - } -}; - export const resetOnePreconfigurationHandler: FleetRequestHandler< TypeOf, undefined, diff --git a/x-pack/plugins/fleet/server/routes/preconfiguration/index.ts b/x-pack/plugins/fleet/server/routes/preconfiguration/index.ts index 38651b15f939d..9f5f372298530 100644 --- a/x-pack/plugins/fleet/server/routes/preconfiguration/index.ts +++ b/x-pack/plugins/fleet/server/routes/preconfiguration/index.ts @@ -6,17 +6,10 @@ */ import { PRECONFIGURATION_API_ROUTES } from '../../constants'; -import { - PutPreconfigurationSchema, - PostResetOnePreconfiguredAgentPoliciesSchema, -} from '../../types'; +import { PostResetOnePreconfiguredAgentPoliciesSchema } from '../../types'; import type { FleetAuthzRouter } from '../security'; -import { - updatePreconfigurationHandler, - resetPreconfigurationHandler, - resetOnePreconfigurationHandler, -} from './handler'; +import { resetPreconfigurationHandler, resetOnePreconfigurationHandler } from './handler'; export const registerRoutes = (router: FleetAuthzRouter) => { router.post( @@ -39,15 +32,4 @@ export const registerRoutes = (router: FleetAuthzRouter) => { }, resetOnePreconfigurationHandler ); - - router.put( - { - path: PRECONFIGURATION_API_ROUTES.UPDATE_PATTERN, - validate: PutPreconfigurationSchema, - fleetAuthz: { - fleet: { all: true }, - }, - }, - updatePreconfigurationHandler - ); }; diff --git a/x-pack/plugins/fleet/server/services/agents/action_runner.ts b/x-pack/plugins/fleet/server/services/agents/action_runner.ts index 41f9a44099b5f..18af331980238 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_runner.ts @@ -113,6 +113,13 @@ export abstract class ActionRunner { if (this.retryParams.retryCount === 3) { const errorMessage = 'Stopping after 3rd retry. Error: ' + error.message; appContextService.getLogger().warn(errorMessage); + + // clean up tasks after 3rd retry reached + await Promise.all([ + this.bulkActionsResolver!.removeIfExists(this.checkTaskId!), + this.bulkActionsResolver!.removeIfExists(this.retryParams.taskId!), + ]); + return; } } else { diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts index 685820613a4be..c1b06af222ed6 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts @@ -360,8 +360,8 @@ export async function fetchArchiveBuffer({ if (!archivePath) { archivePath = `/epr/${pkgName}/${pkgName}-${pkgVersion}.zip`; } - - const archiveUrl = `${getRegistryUrl()}${archivePath}`; + const registryUrl = getRegistryUrl(); + const archiveUrl = `${registryUrl}${archivePath}`; const archiveBuffer = await getResponseStream(archiveUrl).then(streamToBuffer); if (shouldVerify) { diff --git a/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts index 31bb776ee1d69..7886314a5f37c 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/registry_url.ts @@ -8,28 +8,8 @@ import { appContextService } from '../..'; // from https://github.com/elastic/package-registry#docker (maybe from OpenAPI one day) -// the unused variables cause a TS warning about unused values -// chose to comment them out vs @ts-ignore or @ts-expect-error on each line - -const PRODUCTION_REGISTRY_URL_CDN = 'https://epr.elastic.co'; -const STAGING_REGISTRY_URL_CDN = 'https://epr-staging.elastic.co'; -const SNAPSHOT_REGISTRY_URL_CDN = 'https://epr-snapshot.elastic.co'; - -// const PRODUCTION_REGISTRY_URL_NO_CDN = 'https://epr.ea-web.elastic.dev'; -// const STAGING_REGISTRY_URL_NO_CDN = 'https://epr-staging.ea-web.elastic.dev'; -// const SNAPSHOT_REGISTRY_URL_NO_CDN = 'https://epr-snapshot.ea-web.elastic.dev'; - -const getDefaultRegistryUrl = (): string => { - const branch = appContextService.getKibanaBranch(); - const isProduction = appContextService.getIsProductionMode(); - if (!isProduction || branch === 'main') { - return SNAPSHOT_REGISTRY_URL_CDN; - } else if (appContextService.getKibanaVersion().includes('-SNAPSHOT')) { - return STAGING_REGISTRY_URL_CDN; - } else { - return PRODUCTION_REGISTRY_URL_CDN; - } -}; +// Package storage V2 URL +const PACKAGE_STORAGE_REGISTRY_URL = 'https://epr.elastic.co'; export const getRegistryUrl = (): string => { const customUrl = appContextService.getConfig()?.registryUrl; @@ -38,5 +18,5 @@ export const getRegistryUrl = (): string => { return customUrl; } - return getDefaultRegistryUrl(); + return PACKAGE_STORAGE_REGISTRY_URL; }; diff --git a/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts b/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts index 3e3bdc9d3b243..a657ff177b68e 100644 --- a/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts +++ b/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts @@ -106,11 +106,13 @@ export const fleetUsagesSchema: RootSchema = { }, }, }, - agent_versions: { + agents_per_version: { type: 'array', items: { - type: 'keyword', - _meta: { description: 'The agent versions enrolled in this deployment.' }, + properties: { + version: { type: 'keyword' }, + count: { type: 'long' }, + }, }, }, agents_per_policy: { diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts index a3e9524dcd3ca..ae40fbc9009a7 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/component_template_list.test.ts @@ -46,7 +46,16 @@ describe('', () => { isManaged: false, }; - const componentTemplates = [componentTemplate1, componentTemplate2]; + const componentTemplate3: ComponentTemplateListItem = { + name: 'test_component_template_3', + hasMappings: true, + hasAliases: true, + hasSettings: true, + usedBy: ['test_index_template_1', 'test_index_template_2'], + isManaged: false, + }; + + const componentTemplates = [componentTemplate1, componentTemplate2, componentTemplate3]; httpRequestsMockHelpers.setLoadComponentTemplatesResponse(componentTemplates); @@ -63,6 +72,26 @@ describe('', () => { }); }); + test('should sort "Usage count" column by number', async () => { + const { actions, table } = testBed; + + // Sort ascending + await actions.clickTableColumnSortButton(1); + + const { tableCellsValues: ascTableCellsValues } = + table.getMetaData('componentTemplatesTable'); + const ascUsageCountValues = ascTableCellsValues.map((row) => row[2]); + expect(ascUsageCountValues).toEqual(['Not in use', '1', '2']); + + // Sort descending + await actions.clickTableColumnSortButton(1); + + const { tableCellsValues: descTableCellsValues } = + table.getMetaData('componentTemplatesTable'); + const descUsageCountValues = descTableCellsValues.map((row) => row[2]); + expect(descUsageCountValues).toEqual(['2', '1', 'Not in use']); + }); + test('should reload the component templates data', async () => { const { component, actions } = testBed; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts index 9fec92812fd1a..a7087b5b45100 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts @@ -32,7 +32,7 @@ export type ComponentTemplateListTestBed = TestBed { - const { find } = testBed; + const { find, component } = testBed; /** * User Actions @@ -42,7 +42,7 @@ const createActions = (testBed: TestBed) => { }; const clickComponentTemplateAt = async (index: number) => { - const { component, table, router } = testBed; + const { table, router } = testBed; const { rows } = table.getMetaData('componentTemplatesTable'); const componentTemplateLink = findTestSubject( rows[index].reactWrapper, @@ -57,6 +57,13 @@ const createActions = (testBed: TestBed) => { }); }; + const clickTableColumnSortButton = async (index: number) => { + await act(async () => { + find('tableHeaderSortButton').at(index).simulate('click'); + }); + component.update(); + }; + const clickDeleteActionAt = (index: number) => { const { table } = testBed; @@ -70,6 +77,7 @@ const createActions = (testBed: TestBed) => { clickReloadButton, clickComponentTemplateAt, clickDeleteActionAt, + clickTableColumnSortButton, }; }; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx index 5b59523ff2548..83bcc171a4873 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_list/table.tsx @@ -187,7 +187,7 @@ export const ComponentTable: FunctionComponent = ({ name: i18n.translate('xpack.idxMgmt.componentTemplatesList.table.isInUseColumnTitle', { defaultMessage: 'Usage count', }), - sortable: true, + sortable: ({ usedBy }: ComponentTemplateListItem) => usedBy.length, render: (usedBy: string[]) => { if (usedBy.length) { return usedBy.length; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx index a15b1a3016ddb..611666c135a6b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiInMemoryTable } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { isEqual } from 'lodash'; import { HostsTableColumns } from './hosts_table_columns'; import { NoData } from '../../../../components/empty_states'; import { InfraLoadingPanel } from '../../../../components/loading'; @@ -17,6 +18,7 @@ import type { SnapshotMetricType } from '../../../../../common/inventory_models/ import type { InfraTimerangeInput } from '../../../../../common/http_api'; import { useUnifiedSearchContext } from '../hooks/use_unified_search'; import { useSourceContext } from '../../../../containers/metrics_source'; +import { useTableProperties } from '../hooks/use_table_properties_url_state'; const HOST_METRICS: Array<{ type: SnapshotMetricType }> = [ { type: 'rx' }, @@ -30,6 +32,7 @@ const HOST_METRICS: Array<{ type: SnapshotMetricType }> = [ export const HostsTable = () => { const { sourceId } = useSourceContext(); const { buildQuery, dateRangeTimestamp, panelFilters } = useUnifiedSearchContext(); + const [properties, setProperties] = useTableProperties(); const timeRange: InfraTimerangeInput = { from: dateRangeTimestamp.from, @@ -59,6 +62,24 @@ export const HostsTable = () => { const items = useHostTable(nodes); const noData = items.length === 0; + const onTableChange = useCallback( + ({ page = {}, sort = {} }) => { + const { index: pageIndex, size: pageSize } = page; + const { field, direction } = sort; + + const sorting = field && direction ? { field, direction } : true; + const pagination = pageIndex >= 0 && pageSize !== 0 ? { pageIndex, pageSize } : true; + + if (!isEqual(properties.sorting, sorting)) { + setProperties({ sorting }); + } + if (!isEqual(properties.pagination, pagination)) { + setProperties({ pagination }); + } + }, + [setProperties, properties.pagination, properties.sorting] + ); + return ( <> {loading || !panelFilters ? ( @@ -88,7 +109,17 @@ export const HostsTable = () => { /> ) : ( - + )} ); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_table_properties_url_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_table_properties_url_state.ts new file mode 100644 index 0000000000000..980fdf19a684c --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_table_properties_url_state.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as rt from 'io-ts'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; +import { useUrlState } from '../../../../utils/use_url_state'; + +export const GET_DEFAULT_TABLE_PROPERTIES = { + sorting: true, + pagination: true, +}; +const HOST_TABLE_PROPERTIES_URL_STATE_KEY = 'tableProperties'; + +type Action = rt.TypeOf; +type PropertiesUpdater = (newProps: Action) => void; + +export const useTableProperties = (): [TableProperties, PropertiesUpdater] => { + const [urlState, setUrlState] = useUrlState({ + defaultState: GET_DEFAULT_TABLE_PROPERTIES, + decodeUrlState, + encodeUrlState, + urlStateKey: HOST_TABLE_PROPERTIES_URL_STATE_KEY, + }); + + const setProperties = (newProps: Action) => setUrlState({ ...urlState, ...newProps }); + + return [urlState, setProperties]; +}; + +const PaginationRT = rt.union([ + rt.boolean, + rt.partial({ pageIndex: rt.number, pageSize: rt.number }), +]); +const SortingRT = rt.union([rt.boolean, rt.type({ field: rt.string, direction: rt.any })]); + +const SetSortingRT = rt.partial({ + sorting: SortingRT, +}); + +const SetPaginationRT = rt.partial({ + pagination: PaginationRT, +}); + +const ActionRT = rt.intersection([SetSortingRT, SetPaginationRT]); + +const TablePropertiesRT = rt.type({ + pagination: PaginationRT, + sorting: SortingRT, +}); + +type TableProperties = rt.TypeOf; + +const encodeUrlState = TablePropertiesRT.encode; +const decodeUrlState = (value: unknown) => { + return pipe(TablePropertiesRT.decode(value), fold(constant(undefined), identity)); +}; diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx index a1a61c4b8447b..f93ee60bae9e6 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx @@ -328,6 +328,7 @@ export function DimensionEditor(props: DimensionEditorProps) { field: currentField || undefined, filterOperations: props.filterOperations, visualizationGroups: dimensionGroups, + dateRange, }), disabledStatus: definition.getDisabledStatus && diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx index 2be5bc33835b9..3b23657d9598e 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx @@ -1571,6 +1571,23 @@ describe('FormBasedDimensionEditor', () => { .prop('error') ).toBe('Time shift value is not valid.'); }); + + it('should mark absolute time shift as invalid', () => { + const props = getProps({ + timeShift: 'startAt(2022-11-02T00:00:00.000Z)', + }); + wrapper = mount(); + + expect(wrapper.find(TimeShift).find(EuiComboBox).prop('isInvalid')).toBeTruthy(); + + expect( + wrapper + .find(TimeShift) + .find('[data-test-subj="indexPattern-dimension-time-shift-row"]') + .first() + .prop('error') + ).toBe('Time shift value is not valid.'); + }); }); describe('filtering', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.tsx index b124f1ce82afb..f4bb61b9758b6 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/dimension_panel.tsx @@ -52,14 +52,13 @@ function wrapOnDot(str?: string) { export const FormBasedDimensionTriggerComponent = function FormBasedDimensionTrigger( props: FormBasedDimensionTriggerProps ) { - const layerId = props.layerId; + const { columnId, uniqueLabel, invalid, invalidMessage, hideTooltip, layerId, dateRange } = props; const layer = props.state.layers[layerId]; const currentIndexPattern = props.indexPatterns[layer.indexPatternId]; - const { columnId, uniqueLabel, invalid, invalidMessage, hideTooltip } = props; const currentColumnHasErrors = useMemo( - () => invalid || isColumnInvalid(layer, columnId, currentIndexPattern), - [layer, columnId, currentIndexPattern, invalid] + () => invalid || isColumnInvalid(layer, columnId, currentIndexPattern, dateRange), + [layer, columnId, currentIndexPattern, invalid, dateRange] ); const selectedColumn: GenericIndexPatternColumn | null = layer.columns[props.columnId] ?? null; diff --git a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/time_shift.tsx b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/time_shift.tsx index 35f278cbbe988..1d3cfefa66a4d 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/time_shift.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/dimension_panel/time_shift.tsx @@ -9,7 +9,7 @@ import { EuiFormRow, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { EuiComboBox } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; -import { DatatableUtilitiesService, parseTimeShift } from '@kbn/data-plugin/common'; +import { type DatatableUtilitiesService, parseTimeShift } from '@kbn/data-plugin/common'; import { adjustTimeScaleLabelSuffix, GenericIndexPatternColumn, @@ -21,6 +21,7 @@ import { getDateHistogramInterval, getLayerTimeShiftChecks, timeShiftOptions, + getColumnTimeShiftWarnings, } from '../time_shift_utils'; import type { IndexPattern } from '../../../types'; @@ -90,7 +91,7 @@ export function TimeShift({ activeData, layerId ); - const { isValueTooSmall, isValueNotMultiple, isInvalid, canShift } = + const { canShift, isValueTooSmall, isValueNotMultiple, isInvalid } = getLayerTimeShiftChecks(dateHistogramInterval); if (!canShift) { @@ -99,8 +100,7 @@ export function TimeShift({ const parsedLocalValue = localValue && parseTimeShift(localValue); const isLocalValueInvalid = Boolean(parsedLocalValue && isInvalid(parsedLocalValue)); - const localValueTooSmall = parsedLocalValue && isValueTooSmall(parsedLocalValue); - const localValueNotMultiple = parsedLocalValue && isValueNotMultiple(parsedLocalValue); + const warnings = getColumnTimeShiftWarnings(dateHistogramInterval, localValue); function getSelectedOption() { const goodPick = timeShiftOptions.filter(({ value }) => value === localValue); @@ -130,22 +130,13 @@ export function TimeShift({ defaultMessage: 'Enter the time shift number and unit', })} error={ - (localValueTooSmall && - i18n.translate('xpack.lens.indexPattern.timeShift.tooSmallHelp', { - defaultMessage: - 'Time shift should to be larger than the date histogram interval. Either increase time shift or specify smaller interval in date histogram', - })) || - (localValueNotMultiple && - i18n.translate('xpack.lens.indexPattern.timeShift.noMultipleHelp', { - defaultMessage: - 'Time shift should be a multiple of the date histogram interval. Either adjust time shift or date histogram interval', - })) || + warnings[0] || (isLocalValueInvalid && i18n.translate('xpack.lens.indexPattern.timeShift.genericInvalidHelp', { defaultMessage: 'Time shift value is not valid.', })) } - isInvalid={Boolean(isLocalValueInvalid || localValueTooSmall || localValueNotMultiple)} + isInvalid={Boolean(isLocalValueInvalid || warnings.length)} > diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts index fdaf1f51c644b..c31e6a6fa7854 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts @@ -176,6 +176,10 @@ const expectedIndexPatterns = { }; const indexPatterns = expectedIndexPatterns; +const dateRange = { + fromDate: '2022-03-17T08:25:00.000Z', + toDate: '2022-04-17T08:25:00.000Z', +}; describe('IndexPattern Data Source', () => { let baseState: FormBasedPrivateState; @@ -316,7 +320,7 @@ describe('IndexPattern Data Source', () => { it('should generate an empty expression when no columns are selected', async () => { const state = FormBasedDatasource.initialize(); expect( - FormBasedDatasource.toExpression(state, 'first', indexPatterns, 'testing-seed') + FormBasedDatasource.toExpression(state, 'first', indexPatterns, dateRange, 'testing-seed') ).toEqual(null); }); @@ -341,7 +345,13 @@ describe('IndexPattern Data Source', () => { }, }; expect( - FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns, 'testing-seed') + FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + dateRange, + 'testing-seed' + ) ).toEqual({ chain: [ { @@ -390,7 +400,13 @@ describe('IndexPattern Data Source', () => { }; expect( - FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns, 'testing-seed') + FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + dateRange, + 'testing-seed' + ) ).toMatchInlineSnapshot(` Object { "chain": Array [ @@ -575,6 +591,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; expect(ast.chain[1].arguments.timeFields).toEqual(['timestamp', 'another_datefield']); @@ -615,6 +632,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; expect((ast.chain[1].arguments.aggs[1] as Ast).chain[0].arguments.timeShift).toEqual(['1d']); @@ -827,6 +845,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; const count = (ast.chain[1].arguments.aggs[1] as Ast).chain[0]; @@ -896,6 +915,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; expect(ast.chain[1].arguments.aggs[0]).toMatchInlineSnapshot(` @@ -1025,6 +1045,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; const timeScaleCalls = ast.chain.filter((fn) => fn.function === 'lens_time_scale'); @@ -1095,6 +1116,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; const filteredMetricAgg = (ast.chain[1].arguments.aggs[0] as Ast).chain[0].arguments; @@ -1151,6 +1173,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; const formatIndex = ast.chain.findIndex((fn) => fn.function === 'lens_format_column'); @@ -1204,6 +1227,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; expect(ast.chain[1].arguments.metricsAtAllLevels).toEqual([false]); @@ -1248,6 +1272,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; expect(ast.chain[1].arguments.timeFields).toEqual(['timestamp']); @@ -1306,7 +1331,13 @@ describe('IndexPattern Data Source', () => { const optimizeMock = jest.spyOn(operationDefinitionMap.percentile, 'optimizeEsAggs'); - FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns, 'testing-seed'); + FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + dateRange, + 'testing-seed' + ); expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); @@ -1378,6 +1409,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; @@ -1447,6 +1479,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; @@ -1557,6 +1590,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; // @ts-expect-error we can't isolate just the reference type @@ -1595,6 +1629,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; @@ -1687,6 +1722,7 @@ describe('IndexPattern Data Source', () => { queryBaseState, 'first', indexPatterns, + dateRange, 'testing-seed' ) as Ast; const chainLength = ast.chain.length; diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx index 18572222a6c8e..4f90ed840bd0c 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx @@ -416,8 +416,8 @@ export function getFormBasedDatasource({ return fields; }, - toExpression: (state, layerId, indexPatterns, searchSessionId) => - toExpression(state, layerId, indexPatterns, uiSettings, searchSessionId), + toExpression: (state, layerId, indexPatterns, dateRange, searchSessionId) => + toExpression(state, layerId, indexPatterns, uiSettings, dateRange, searchSessionId), renderLayerSettings( domElement: Element, @@ -513,10 +513,10 @@ export function getFormBasedDatasource({ return columnLabelMap; }, - isValidColumn: (state, indexPatterns, layerId, columnId) => { + isValidColumn: (state, indexPatterns, layerId, columnId, dateRange) => { const layer = state.layers[layerId]; - return !isColumnInvalid(layer, columnId, indexPatterns[layer.indexPatternId]); + return !isColumnInvalid(layer, columnId, indexPatterns[layer.indexPatternId], dateRange); }, renderDimensionTrigger: ( diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx index 7fa1a5edb4f7d..241d32c9c1a79 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx @@ -107,6 +107,7 @@ export function FormulaEditor({ setIsCloseable, dateHistogramInterval, hasData, + dateRange, }: Omit, 'activeData'> & { dateHistogramInterval: ReturnType; hasData: boolean; @@ -189,6 +190,7 @@ export function FormulaEditor({ { indexPattern, operations: operationDefinitionMap, + dateRange, } ).layer ); @@ -218,6 +220,7 @@ export function FormulaEditor({ { indexPattern, operations: operationDefinitionMap, + dateRange, } ).layer ); @@ -237,7 +240,8 @@ export function FormulaEditor({ layer, indexPattern, visibleOperationsMap, - currentColumn + currentColumn, + dateRange ); if (validationErrors.length) { errors = validationErrors; @@ -267,6 +271,7 @@ export function FormulaEditor({ { indexPattern, operations: operationDefinitionMap, + dateRange, } ).layer ); @@ -332,6 +337,7 @@ export function FormulaEditor({ { indexPattern, operations: operationDefinitionMap, + dateRange, } ); @@ -348,6 +354,7 @@ export function FormulaEditor({ newLayer, id, indexPattern, + dateRange, visibleOperationsMap ); if (messages) { @@ -367,14 +374,16 @@ export function FormulaEditor({ const startPosition = offsetToRowColumn(text, locations[id].min); const endPosition = offsetToRowColumn(text, locations[id].max); newWarnings.push( - ...getColumnTimeShiftWarnings(dateHistogramInterval, column).map((message) => ({ - message, - startColumn: startPosition.column + 1, - startLineNumber: startPosition.lineNumber, - endColumn: endPosition.column + 1, - endLineNumber: endPosition.lineNumber, - severity: monaco.MarkerSeverity.Warning, - })) + ...getColumnTimeShiftWarnings(dateHistogramInterval, column.timeShift).map( + (message) => ({ + message, + startColumn: startPosition.column + 1, + startLineNumber: startPosition.lineNumber, + endColumn: endPosition.column + 1, + endLineNumber: endPosition.lineNumber, + severity: monaco.MarkerSeverity.Warning, + }) + ) ); } } @@ -442,6 +451,7 @@ export function FormulaEditor({ unifiedSearch, dataViews, dateHistogramInterval: baseIntervalRef.current, + dateRange, }); } } else { @@ -454,6 +464,7 @@ export function FormulaEditor({ unifiedSearch, dataViews, dateHistogramInterval: baseIntervalRef.current, + dateRange, }); } @@ -469,7 +480,7 @@ export function FormulaEditor({ ), }; }, - [indexPattern, visibleOperationsMap, unifiedSearch, dataViews, baseIntervalRef] + [indexPattern, visibleOperationsMap, unifiedSearch, dataViews, baseIntervalRef, dateRange] ); const provideSignatureHelp = useCallback( diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts index d2237bea0f39e..bb91fc4a7b3ac 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts @@ -202,21 +202,41 @@ describe('math completion', () => { }); describe('autocomplete', () => { - it('should list all valid functions at the top level (fake test)', async () => { - // This test forces an invalid scenario, since the autocomplete actually requires - // some typing - const results = await suggest({ - expression: '', - zeroIndexedOffset: 1, + const dateRange = { fromDate: '2022-11-01T00:00:00.000Z', toDate: '2022-11-03T00:00:00.000Z' }; + + function getSuggestionArgs({ + expression, + zeroIndexedOffset, + triggerCharacter, + }: { + expression: string; + zeroIndexedOffset: number; + triggerCharacter: string; + }) { + return { + expression, + zeroIndexedOffset, context: { triggerKind: monaco.languages.CompletionTriggerKind.TriggerCharacter, - triggerCharacter: '', + triggerCharacter, }, indexPattern: createMockedIndexPattern(), operationDefinitionMap, unifiedSearch: unifiedSearchPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), - }); + dateRange, + }; + } + it('should list all valid functions at the top level (fake test)', async () => { + // This test forces an invalid scenario, since the autocomplete actually requires + // some typing + const results = await suggest( + getSuggestionArgs({ + expression: '', + zeroIndexedOffset: 1, + triggerCharacter: '', + }) + ); expect(results.list).toHaveLength(4 + Object.keys(tinymathFunctions).length); ['sum', 'moving_average', 'cumulative_sum', 'last_value'].forEach((key) => { expect(results.list).toEqual(expect.arrayContaining([{ label: key, type: 'operation' }])); @@ -227,18 +247,13 @@ describe('math completion', () => { }); it('should list all valid sub-functions for a fullReference', async () => { - const results = await suggest({ - expression: 'moving_average()', - zeroIndexedOffset: 15, - context: { - triggerKind: monaco.languages.CompletionTriggerKind.TriggerCharacter, + const results = await suggest( + getSuggestionArgs({ + expression: 'moving_average()', + zeroIndexedOffset: 15, triggerCharacter: '(', - }, - indexPattern: createMockedIndexPattern(), - operationDefinitionMap, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), - }); + }) + ); expect(results.list).toHaveLength(2); ['sum', 'last_value'].forEach((key) => { expect(results.list).toEqual(expect.arrayContaining([{ label: key, type: 'operation' }])); @@ -246,50 +261,35 @@ describe('math completion', () => { }); it('should list all valid named arguments for a fullReference', async () => { - const results = await suggest({ - expression: 'moving_average(count(),)', - zeroIndexedOffset: 23, - context: { - triggerKind: monaco.languages.CompletionTriggerKind.TriggerCharacter, + const results = await suggest( + getSuggestionArgs({ + expression: 'moving_average(count(),)', + zeroIndexedOffset: 23, triggerCharacter: ',', - }, - indexPattern: createMockedIndexPattern(), - operationDefinitionMap, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), - }); + }) + ); expect(results.list).toEqual(['window']); }); it('should not list named arguments when they are already in use', async () => { - const results = await suggest({ - expression: 'moving_average(count(), window=5, )', - zeroIndexedOffset: 34, - context: { - triggerKind: monaco.languages.CompletionTriggerKind.TriggerCharacter, + const results = await suggest( + getSuggestionArgs({ + expression: 'moving_average(count(), window=5, )', + zeroIndexedOffset: 34, triggerCharacter: ',', - }, - indexPattern: createMockedIndexPattern(), - operationDefinitionMap, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), - }); + }) + ); expect(results.list).toEqual([]); }); it('should list all valid positional arguments for a tinymath function used by name', async () => { - const results = await suggest({ - expression: 'divide(count(), )', - zeroIndexedOffset: 16, - context: { - triggerKind: monaco.languages.CompletionTriggerKind.TriggerCharacter, + const results = await suggest( + getSuggestionArgs({ + expression: 'divide(count(), )', + zeroIndexedOffset: 16, triggerCharacter: ',', - }, - indexPattern: createMockedIndexPattern(), - operationDefinitionMap, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), - }); + }) + ); expect(results.list).toHaveLength(4 + Object.keys(tinymathFunctions).length); ['sum', 'moving_average', 'cumulative_sum', 'last_value'].forEach((key) => { expect(results.list).toEqual(expect.arrayContaining([{ label: key, type: 'math' }])); @@ -300,18 +300,13 @@ describe('math completion', () => { }); it('should list all valid positional arguments for a tinymath function used with alias', async () => { - const results = await suggest({ - expression: 'count() / ', - zeroIndexedOffset: 10, - context: { - triggerKind: monaco.languages.CompletionTriggerKind.TriggerCharacter, + const results = await suggest( + getSuggestionArgs({ + expression: 'count() / ', + zeroIndexedOffset: 10, triggerCharacter: ',', - }, - indexPattern: createMockedIndexPattern(), - operationDefinitionMap, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), - }); + }) + ); expect(results.list).toHaveLength(4 + Object.keys(tinymathFunctions).length); ['sum', 'moving_average', 'cumulative_sum', 'last_value'].forEach((key) => { expect(results.list).toEqual(expect.arrayContaining([{ label: key, type: 'math' }])); @@ -322,52 +317,87 @@ describe('math completion', () => { }); it('should not autocomplete any fields for the count function', async () => { - const results = await suggest({ - expression: 'count()', - zeroIndexedOffset: 6, - context: { - triggerKind: monaco.languages.CompletionTriggerKind.TriggerCharacter, + const results = await suggest( + getSuggestionArgs({ + expression: 'count()', + zeroIndexedOffset: 6, triggerCharacter: '(', - }, - indexPattern: createMockedIndexPattern(), - operationDefinitionMap, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), - }); + }) + ); expect(results.list).toHaveLength(0); }); it('should autocomplete and validate the right type of field', async () => { - const results = await suggest({ - expression: 'sum()', - zeroIndexedOffset: 4, - context: { - triggerKind: monaco.languages.CompletionTriggerKind.TriggerCharacter, + const results = await suggest( + getSuggestionArgs({ + expression: 'sum()', + zeroIndexedOffset: 4, triggerCharacter: '(', - }, - indexPattern: createMockedIndexPattern(), - operationDefinitionMap, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), - }); + }) + ); expect(results.list).toEqual(['bytes', 'memory']); }); it('should autocomplete only operations that provide numeric or date output', async () => { - const results = await suggest({ - expression: 'last_value()', - zeroIndexedOffset: 11, - context: { - triggerKind: monaco.languages.CompletionTriggerKind.TriggerCharacter, + const results = await suggest( + getSuggestionArgs({ + expression: 'last_value()', + zeroIndexedOffset: 11, triggerCharacter: '(', - }, - indexPattern: createMockedIndexPattern(), - operationDefinitionMap, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), - }); + }) + ); expect(results.list).toEqual(['bytes', 'memory', 'timestamp', 'start_date']); }); + + it('should autocomplete shift parameter with relative suggestions and a couple of abs ones', async () => { + const results = await suggest( + getSuggestionArgs({ + expression: `count(shift='')`, + zeroIndexedOffset: 13, + triggerCharacter: '=', + }) + ); + expect(results.list).toEqual([ + '', + '1h', + '3h', + '6h', + '12h', + '1d', + '1w', + '1M', + '3M', + '6M', + '1y', + 'previous', + 'startAt(2022-11-01T00:00:00.000Z)', + 'endAt(2022-11-03T00:00:00.000Z)', + ]); + }); + + it('should autocomplete shift parameter with absolute suggestions once detected', async () => { + const results = await suggest( + getSuggestionArgs({ + expression: `count(shift='endAt(')`, + zeroIndexedOffset: 19, + triggerCharacter: '=', + }) + ); + expect(results.list).toEqual([ + '2022-11-03T00:00:00.000Z)', + '2022-11-02T23:00:00.000Z)', + '2022-11-02T21:00:00.000Z)', + '2022-11-02T18:00:00.000Z)', + '2022-11-02T12:00:00.000Z)', + '2022-11-02T00:00:00.000Z)', + '2022-10-27T00:00:00.000Z)', + '2022-10-03T00:00:00.000Z)', + '2022-08-03T00:00:00.000Z)', + '2022-05-03T00:00:00.000Z)', + '2021-11-03T00:00:00.000Z)', + '2022-11-03T00:00:00.000Z)', + ]); + }); }); describe('offsetToRowColumn', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts index de23e8c4f1bbe..a0a968a136886 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts @@ -22,6 +22,8 @@ import type { } from '@kbn/unified-search-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { parseTimeShift } from '@kbn/data-plugin/common'; +import moment from 'moment'; +import { DateRange } from '../../../../../../../common/types'; import type { IndexPattern } from '../../../../../../types'; import { memoizedGetAvailableOperationsByMetadata } from '../../../operations'; import { tinymathFunctions, groupArgsByType, unquotedStringRegex, nonNullable } from '../util'; @@ -146,6 +148,7 @@ export async function suggest({ dataViews, unifiedSearch, dateHistogramInterval, + dateRange, }: { expression: string; zeroIndexedOffset: number; @@ -155,6 +158,7 @@ export async function suggest({ unifiedSearch: UnifiedSearchPublicPluginStart; dataViews: DataViewsPublicPluginStart; dateHistogramInterval?: number; + dateRange: DateRange; }): Promise { const text = expression.substr(0, zeroIndexedOffset) + MARKER + expression.substr(zeroIndexedOffset); @@ -177,6 +181,7 @@ export async function suggest({ dataViews, indexPattern, dateHistogramInterval, + dateRange, }); } else if (tokenInfo?.parent) { return getArgumentSuggestions( @@ -362,32 +367,55 @@ function getArgumentSuggestions( return { list: [], type: SUGGESTION_TYPE.FIELD }; } +const anchoredAbsoluteTimeShiftRegexp = /^(start|end)At\(/; + +function computeAbsSuggestion(dateRange: DateRange, prefix: string, value: string) { + const refDate = prefix.startsWith('s') ? dateRange.fromDate : dateRange.toDate; + return moment(refDate).subtract(parseTimeShift(value), 'ms').toISOString(); +} + export async function getNamedArgumentSuggestions({ ast, unifiedSearch, dataViews, indexPattern, dateHistogramInterval, + dateRange, }: { ast: TinymathNamedArgument; indexPattern: IndexPattern; unifiedSearch: UnifiedSearchPublicPluginStart; dataViews: DataViewsPublicPluginStart; dateHistogramInterval?: number; + dateRange: DateRange; }) { if (ast.name === 'shift') { + const validTimeShiftOptions = timeShiftOptions + .filter(({ value }) => { + if (dateHistogramInterval == null) return true; + const parsedValue = parseTimeShift(value); + return ( + parsedValue !== 'previous' && + (parsedValue === 'invalid' || + Number.isInteger(parsedValue.asMilliseconds() / dateHistogramInterval)) + ); + }) + .map(({ value }) => value); + const absShift = ast.value.split(MARKER)[0]; + // Translate the relative time shifts into absolute ones + if (anchoredAbsoluteTimeShiftRegexp.test(absShift)) { + return { + list: validTimeShiftOptions.map( + (value) => `${computeAbsSuggestion(dateRange, absShift, value)})` + ), + type: SUGGESTION_TYPE.SHIFTS, + }; + } + const extraAbsSuggestions = ['startAt', 'endAt'].map( + (prefix) => `${prefix}(${computeAbsSuggestion(dateRange, prefix, validTimeShiftOptions[0])})` + ); return { - list: timeShiftOptions - .filter(({ value }) => { - if (typeof dateHistogramInterval === 'undefined') return true; - const parsedValue = parseTimeShift(value); - return ( - parsedValue !== 'previous' && - (parsedValue === 'invalid' || - Number.isInteger(parsedValue.asMilliseconds() / dateHistogramInterval)) - ); - }) - .map(({ value }) => value), + list: validTimeShiftOptions.concat(extraAbsSuggestions), type: SUGGESTION_TYPE.SHIFTS, }; } diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx index 0925b5cbcb0d2..281f7007e8b97 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.test.tsx @@ -27,7 +27,14 @@ jest.mock('../../layer_helpers', () => { getColumnOrder: jest.fn(({ columns }: { columns: Record }) => Object.keys(columns) ), - getManagedColumnsFrom: jest.fn().mockReturnValue([]), + getManagedColumnsFrom: jest + .fn() + .mockImplementation( + ( + id: string, + columns: Record> + ) => columns[id].references?.map((colId) => [colId, {}]) || [] + ), }; }); @@ -893,7 +900,7 @@ describe('formula', () => { function getNewLayerWithFormula( formula: string, isBroken = true, - columnParams: Partial> = {} + columnParams: Partial> = {} ): FormBasedLayer { return { columns: { @@ -922,6 +929,7 @@ describe('formula', () => { getNewLayerWithFormula('count()'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -933,6 +941,7 @@ describe('formula', () => { getNewLayerWithFormula(`count(kql='*')`, false), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -944,6 +953,7 @@ describe('formula', () => { getNewLayerWithFormula(`count(kql='invalid: "')`, false), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([ @@ -959,6 +969,7 @@ invalid: " getNewLayerWithFormula('average(bytes)'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -968,6 +979,7 @@ invalid: " getNewLayerWithFormula('average("bytes")'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -979,6 +991,7 @@ invalid: " getNewLayerWithFormula('derivative(average(bytes))'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -988,6 +1001,7 @@ invalid: " getNewLayerWithFormula('derivative(average("bytes"))'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -999,6 +1013,7 @@ invalid: " getNewLayerWithFormula('moving_average(average(bytes), window=7)'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -1010,6 +1025,7 @@ invalid: " getNewLayerWithFormula('bytes'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([`The field bytes cannot be used without operation`]); @@ -1019,6 +1035,7 @@ invalid: " getNewLayerWithFormula('bytes + bytes'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([`The operation add does not accept any field as argument`]); @@ -1040,6 +1057,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([`The Formula ${formula} cannot be parsed`]); @@ -1060,6 +1078,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(['Field noField not found']); @@ -1075,6 +1094,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(['Fields noField, noField2 not found']); @@ -1090,6 +1110,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(['Operation noFn not found']); @@ -1103,6 +1124,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(['Operations noFn, noFnTwo not found']); @@ -1118,6 +1140,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(['Operation formula not found']); @@ -1131,6 +1154,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(['Operation math not found']); @@ -1154,6 +1178,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual( @@ -1173,6 +1198,7 @@ invalid: " getNewLayerWithFormula('count()'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -1184,6 +1210,7 @@ invalid: " getNewLayerWithFormula('moving_average(average(bytes))'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([ @@ -1195,6 +1222,7 @@ invalid: " getNewLayerWithFormula('moving_average(average(bytes), myparam=7)'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([ @@ -1208,6 +1236,7 @@ invalid: " getNewLayerWithFormula('average(bytes, myparam=7)'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(['The operation average does not accept any parameter']); @@ -1228,6 +1257,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual( @@ -1258,6 +1288,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual( @@ -1276,6 +1307,7 @@ invalid: " getNewLayerWithFormula('moving_average(average(bytes), window="m")'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([ @@ -1295,6 +1327,7 @@ invalid: " `), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -1315,6 +1348,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -1353,6 +1387,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(expect.arrayContaining([expect.stringMatching(`Single quotes are required`)])); @@ -1383,6 +1418,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([`The Formula ${formula} cannot be parsed`]); @@ -1401,6 +1437,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -1413,6 +1450,7 @@ invalid: " getNewLayerWithFormula(`count(kql='category.keyword: *', lucene='category.keyword: *')`), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(['Use only one of kql= or lucene=, not both']); @@ -1425,6 +1463,7 @@ invalid: " getNewLayerWithFormula(`${fn}()`), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([`The first argument for ${fn} should be a field name. Found no field`]); @@ -1434,6 +1473,7 @@ invalid: " getNewLayerWithFormula(`sum(kql='category.keyword: *')`), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([`The first argument for sum should be a field name. Found category.keyword: *`]); @@ -1446,6 +1486,7 @@ invalid: " getNewLayerWithFormula(`${fn}()`), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([`The first argument for ${fn} should be a operation name. Found no operation`]); @@ -1453,9 +1494,11 @@ invalid: " }); it('returns an error if the formula is fully static and there is at least one bucket dimension', () => { - const formulaLayer = getNewLayerWithFormula('5 + 3 * 7'); + const formulaLayer = getNewLayerWithFormula('5 + 3 * 7', false, { references: ['col1X'] }); expect( formulaOperation.getErrorMessage!( + // this became a little bit more tricker as now it takes into account the number of references + // for the error { ...formulaLayer, columns: { @@ -1473,6 +1516,7 @@ invalid: " }, 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([ @@ -1486,6 +1530,7 @@ invalid: " getNewLayerWithFormula('5 + 3 * 7'), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -1502,6 +1547,7 @@ invalid: " getNewLayerWithFormula(`ifelse(${formula}, 1, 5)`), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -1520,6 +1566,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -1538,6 +1585,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); @@ -1560,6 +1608,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([ @@ -1633,6 +1682,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toContain(errors[i](fn)); @@ -1653,6 +1703,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([ @@ -1697,6 +1748,7 @@ invalid: " getNewLayerWithFormula(`${fn}(${cond}, ${left}, ${right})`), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual( @@ -1723,6 +1775,7 @@ invalid: " getNewLayerWithFormula(formula), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([errorsWithSuggestions[i]]); @@ -1751,6 +1804,7 @@ invalid: " }), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([ @@ -1771,6 +1825,7 @@ invalid: " ), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual([ @@ -1795,6 +1850,7 @@ invalid: " }), 'col1', indexPattern, + undefined, operationDefinitionMap ) ).toEqual(undefined); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx index 577b68b98675d..a1af332390329 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula.tsx @@ -62,7 +62,7 @@ export const formulaOperation: OperationDefinition { const def = visibleOperationsMap[col.operationType]; if (def?.getErrorMessage) { - const messages = def.getErrorMessage(layer, id, indexPattern, visibleOperationsMap); + const messages = def.getErrorMessage( + layer, + id, + indexPattern, + dateRange, + visibleOperationsMap + ); return messages ? { message: messages.join(', ') } : []; } return []; @@ -102,8 +115,13 @@ export const formulaOperation: OperationDefinition ({ - insertOrReplaceFormulaColumn: jest.fn().mockReturnValue({}), -})); +import moment from 'moment'; +import type { FormulaIndexPatternColumn } from './formula'; + +jest.mock('./parse', () => { + const original = jest.requireActual('./parse'); + return { + ...original, + insertOrReplaceFormulaColumn: jest.fn((...args) => + original.insertOrReplaceFormulaColumn(...args) + ), + }; +}); jest.mock('../../../../../data_views_service/loader', () => ({ convertDataViewIntoLensIndexPattern: jest.fn((v) => v), @@ -153,4 +161,80 @@ describe('createFormulaPublicApi', () => { { indexPattern: {} } ); }); + + test('should accept an absolute time shift for shiftable operations', () => { + const baseLayer = getBaseLayer(); + + const dateString = '2022-11-02T00:00:00.000Z'; + // shift by 2 days + 2500 s (to get a shift which is not a multiple of the given interval) + const shiftedDate = moment(dateString).subtract(175300, 's').toISOString(); + + const result = publicApiHelper.insertOrReplaceFormulaColumn( + 'col', + { + formula: `count(shift='startAt(${shiftedDate})') - count(shift='endAt(${shiftedDate})')`, + }, + baseLayer, + dataView + ); + expect((result?.columns.col as FormulaIndexPatternColumn).params.isFormulaBroken).toBe(false); + }); + + test('should perform more validations for absolute time shifts if dateRange is passed', () => { + const baseLayer = getBaseLayer(); + + const dateString = '2022-11-02T00:00:00.000Z'; + // date in the future + const shiftedDate = '3022-11-02T00:00:00.000Z'; + + const dateRange = { + fromDate: moment(dateString).subtract('1', 'd').toISOString(), + toDate: moment(dateString).add('1', 'd').toISOString(), + }; + + const result = publicApiHelper.insertOrReplaceFormulaColumn( + 'col', + { + formula: `count(shift='startAt(${shiftedDate})') - count(shift='endAt(${shiftedDate})')`, + }, + baseLayer, + dataView, + dateRange + ); + + expect((result?.columns.col as FormulaIndexPatternColumn).params.isFormulaBroken).toBe(true); + }); + + test('should perform format-only validation if no date range is passed', () => { + const baseLayer = getBaseLayer(); + + const result = publicApiHelper.insertOrReplaceFormulaColumn( + 'col', + { + formula: `count(shift='startAt(invalid)') - count(shift='endAt(3022)')`, + }, + baseLayer, + dataView + ); + + expect((result?.columns.col as FormulaIndexPatternColumn).params.isFormulaBroken).toBe(true); + }); + + test('should not detect date in the future error if no date range is passed', () => { + const baseLayer = getBaseLayer(); + + // date in the future + const shiftedDate = '3022-11-02T00:00:00.000Z'; + + const result = publicApiHelper.insertOrReplaceFormulaColumn( + 'col', + { + formula: `count(shift='startAt(${shiftedDate})') - count(shift='endAt(${shiftedDate})')`, + }, + baseLayer, + dataView + ); + + expect((result?.columns.col as FormulaIndexPatternColumn).params.isFormulaBroken).toBe(false); + }); }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.ts index 56469d61ad8f3..4ce14d3f71142 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.ts @@ -7,6 +7,7 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import { Query } from '@kbn/es-query'; +import type { DateRange } from '../../../../../../common'; import { convertDataViewIntoLensIndexPattern } from '../../../../../data_views_service/loader'; import type { IndexPattern } from '../../../../../types'; import type { PersistedIndexPatternLayer } from '../../../types'; @@ -44,7 +45,8 @@ export interface FormulaPublicApi { }; }, layer: PersistedIndexPatternLayer, - dataView: DataView + dataView: DataView, + dateRange?: DateRange ) => PersistedIndexPatternLayer | undefined; } @@ -67,7 +69,8 @@ export const createFormulaPublicApi = (): FormulaPublicApi => { id, { formula, label, format, filter, reducedTimeRange, timeScale }, layer, - dataView + dataView, + dateRange ) => { const indexPattern = getCachedLensIndexPattern(dataView); @@ -89,7 +92,7 @@ export const createFormulaPublicApi = (): FormulaPublicApi => { }, }, { ...layer, indexPatternId: indexPattern.id }, - { indexPattern } + { indexPattern, dateRange } ).layer; }, }; diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/parse.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/parse.ts index ea204295500d4..57a529df033c4 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/parse.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/parse.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { isObject } from 'lodash'; import type { TinymathAST, TinymathVariable, TinymathLocation } from '@kbn/tinymath'; +import type { DateRange } from '../../../../../../common/types'; import type { IndexPattern } from '../../../../../types'; import { OperationDefinition, @@ -41,6 +42,7 @@ function parseAndExtract( columnId: string, indexPattern: IndexPattern, operations: Record, + dateRange: DateRange | undefined, label?: string ) { const { root, error } = tryToParse(text, operations); @@ -48,7 +50,14 @@ function parseAndExtract( return { extracted: [], isValid: false }; } // before extracting the data run the validation task and throw if invalid - const errors = runASTValidation(root, layer, indexPattern, operations, layer.columns[columnId]); + const errors = runASTValidation( + root, + layer, + indexPattern, + operations, + layer.columns[columnId], + dateRange + ); if (errors.length) { return { extracted: [], isValid: false }; } @@ -210,6 +219,8 @@ function extractColumns( interface ExpandColumnProperties { indexPattern: IndexPattern; operations?: Record; + dateRange?: DateRange; + strictShiftValidation?: boolean; } const getEmptyColumnsWithFormulaMeta = (): { @@ -228,7 +239,7 @@ function generateFormulaColumns( id: string, column: FormulaIndexPatternColumn, layer: FormBasedLayer, - { indexPattern, operations = operationDefinitionMap }: ExpandColumnProperties + { indexPattern, operations = operationDefinitionMap, dateRange }: ExpandColumnProperties ) { const { columns, meta } = getEmptyColumnsWithFormulaMeta(); const formula = column.params.formula || ''; @@ -239,6 +250,7 @@ function generateFormulaColumns( id, indexPattern, filterByVisibleOperation(operations), + dateRange, column.customLabel ? column.label : undefined ); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts index 1601e53fef33d..0951f950310cb 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts @@ -11,7 +11,14 @@ import { parse, TinymathLocation, TinymathVariable } from '@kbn/tinymath'; import type { TinymathAST, TinymathFunction, TinymathNamedArgument } from '@kbn/tinymath'; import { luceneStringToDsl, toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; import type { Query } from '@kbn/es-query'; -import { parseTimeShift } from '@kbn/data-plugin/common'; +import { + isAbsoluteTimeShift, + parseTimeShift, + REASON_IDS, + REASON_ID_TYPES, + validateAbsoluteTimeShift, +} from '@kbn/data-plugin/common'; +import { DateRange } from '../../../../../../common/types'; import { findMathNodes, findVariables, @@ -437,12 +444,13 @@ export function runASTValidation( layer: FormBasedLayer, indexPattern: IndexPattern, operations: Record, - currentColumn: GenericIndexPatternColumn + currentColumn: GenericIndexPatternColumn, + dateRange?: DateRange ) { return [ ...checkMissingVariableOrFunctions(ast, layer, indexPattern, operations), ...checkTopNodeReturnType(ast), - ...runFullASTValidation(ast, layer, indexPattern, operations, currentColumn), + ...runFullASTValidation(ast, layer, indexPattern, operations, dateRange, currentColumn), ]; } @@ -508,9 +516,31 @@ function checkMissingVariableOrFunctions( return [...missingErrors, ...invalidVariableErrors]; } +function getAbsoluteTimeShiftErrorMessage(reason: REASON_ID_TYPES) { + switch (reason) { + case REASON_IDS.missingTimerange: + return i18n.translate('xpack.lens.indexPattern.absoluteMissingTimeRange', { + defaultMessage: 'Invalid time shift. No time range found as reference', + }); + case REASON_IDS.invalidDate: + return i18n.translate('xpack.lens.indexPattern.absoluteInvalidDate', { + defaultMessage: 'Invalid time shift. The date is not of the correct format', + }); + case REASON_IDS.shiftAfterTimeRange: + return i18n.translate('xpack.lens.indexPattern.absoluteAfterTimeRange', { + defaultMessage: 'Invalid time shift. The provided date is after the current time range', + }); + case REASON_IDS.notAbsoluteTimeShift: + return i18n.translate('xpack.lens.indexPattern.notAbsoluteTimeShift', { + defaultMessage: 'Invalid time shift.', + }); + } +} + function getQueryValidationErrors( namedArguments: TinymathNamedArgument[] | undefined, - indexPattern: IndexPattern + indexPattern: IndexPattern, + dateRange: DateRange | undefined ): ErrorWrapper[] { const errors: ErrorWrapper[] = []; (namedArguments ?? []).forEach((arg) => { @@ -530,16 +560,34 @@ function getQueryValidationErrors( if (arg.name === 'shift') { const parsedShift = parseTimeShift(arg.value); if (parsedShift === 'invalid') { - errors.push({ - message: i18n.translate('xpack.lens.indexPattern.invalidTimeShift', { - defaultMessage: - 'Invalid time shift. Enter positive integer amount followed by one of the units s, m, h, d, w, M, y. For example 3h for 3 hours', - }), - locations: [arg.location], - }); + if (isAbsoluteTimeShift(arg.value)) { + // try to parse as absolute time shift + const error = validateAbsoluteTimeShift( + arg.value, + dateRange + ? { + from: dateRange.fromDate, + to: dateRange.toDate, + } + : undefined + ); + if (error) { + errors.push({ + message: getAbsoluteTimeShiftErrorMessage(error), + locations: [arg.location], + }); + } + } else { + errors.push({ + message: i18n.translate('xpack.lens.indexPattern.invalidTimeShift', { + defaultMessage: + 'Invalid time shift. Enter positive integer amount followed by one of the units s, m, h, d, w, M, y. For example 3h for 3 hours', + }), + locations: [arg.location], + }); + } } } - if (arg.name === 'reducedTimeRange') { const parsedReducedTimeRange = parseTimeShift(arg.value || ''); if (parsedReducedTimeRange === 'invalid' || parsedReducedTimeRange === 'previous') { @@ -600,7 +648,8 @@ function validateNameArguments( | OperationDefinition | OperationDefinition, namedArguments: TinymathNamedArgument[] | undefined, - indexPattern: IndexPattern + indexPattern: IndexPattern, + dateRange: DateRange | undefined ) { const errors = []; const missingParams = getMissingParams(nodeOperation, namedArguments); @@ -642,7 +691,7 @@ function validateNameArguments( }) ); } - const queryValidationErrors = getQueryValidationErrors(namedArguments, indexPattern); + const queryValidationErrors = getQueryValidationErrors(namedArguments, indexPattern, dateRange); if (queryValidationErrors.length) { errors.push(...queryValidationErrors); } @@ -685,6 +734,7 @@ function runFullASTValidation( layer: FormBasedLayer, indexPattern: IndexPattern, operations: Record, + dateRange?: DateRange, currentColumn?: GenericIndexPatternColumn ): ErrorWrapper[] { const missingVariables = findVariables(ast).filter( @@ -783,7 +833,8 @@ function runFullASTValidation( node, nodeOperation, namedArguments, - indexPattern + indexPattern, + dateRange ); const filtersErrors = validateFiltersArguments( @@ -860,7 +911,8 @@ function runFullASTValidation( node, nodeOperation, namedArguments, - indexPattern + indexPattern, + dateRange ); const filtersErrors = validateFiltersArguments( node, diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts index 628a7be8b6752..844386488d906 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/index.ts @@ -310,6 +310,7 @@ interface BaseOperationDefinitionProps< layer: FormBasedLayer, columnId: string, indexPattern: IndexPattern, + dateRange?: DateRange, operationDefinitionMap?: Record ) => | Array< diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx index 6d79d19f44a53..e995b9b27a49d 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx @@ -32,6 +32,10 @@ jest.mock('lodash', () => { }); const uiSettingsMock = {} as IUiSettingsClient; +const dateRange = { + fromDate: '2022-03-17T08:25:00.000Z', + toDate: '2022-04-17T08:25:00.000Z', +}; const defaultProps = { storage: {} as IStorageWrapper, @@ -152,7 +156,8 @@ describe('static_value', () => { staticValueOperation.getErrorMessage!( getLayerWithStaticValue('23'), 'col2', - createMockedIndexPattern() + createMockedIndexPattern(), + dateRange ) ).toBeUndefined(); // test for potential falsy value @@ -160,7 +165,8 @@ describe('static_value', () => { staticValueOperation.getErrorMessage!( getLayerWithStaticValue('0'), 'col2', - createMockedIndexPattern() + createMockedIndexPattern(), + dateRange ) ).toBeUndefined(); }); @@ -172,7 +178,8 @@ describe('static_value', () => { staticValueOperation.getErrorMessage!( getLayerWithStaticValue(value), 'col2', - createMockedIndexPattern() + createMockedIndexPattern(), + dateRange ) ).toEqual(expect.arrayContaining([expect.stringMatching('is not a valid number')])); } @@ -183,7 +190,8 @@ describe('static_value', () => { staticValueOperation.getErrorMessage!( getLayerWithStaticValue(value), 'col2', - createMockedIndexPattern() + createMockedIndexPattern(), + dateRange ) ).toBe(undefined); }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.test.ts index 65b11c2605cc3..ae373cf01d0d0 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.test.ts @@ -42,6 +42,9 @@ import { IndexPattern } from '../../../types'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; const dataMock = dataPluginMock.createStartContract(); +dataMock.query.timefilter.timefilter.getAbsoluteTime = jest + .fn() + .mockReturnValue({ from: '2022-11-01T00:00:00.000Z', to: '2022-11-03T00:00:00.000Z' }); jest.mock('.'); jest.mock('../../../id_generator'); @@ -3202,6 +3205,10 @@ describe('state_helpers', () => { }, 'col1', indexPattern, + { + fromDate: '2022-11-01T00:00:00.000Z', + toDate: '2022-11-03T00:00:00.000Z', + }, operationDefinitionMap ); }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts index 9c2b41ce94798..ad324491a63f9 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/layer_helpers.ts @@ -10,6 +10,7 @@ import { CoreStart } from '@kbn/core/public'; import type { Query } from '@kbn/es-query'; import memoizeOne from 'memoize-one'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { DateRange } from '../../../../common'; import type { DatasourceFixAction, FrameDatasourceAPI, @@ -903,8 +904,10 @@ export function canTransition({ indexPattern, filterOperations, visualizationGroups, + dateRange, }: ColumnChange & { filterOperations: (meta: OperationMetadata) => boolean; + dateRange: DateRange; }): boolean { const previousColumn = layer.columns[columnId]; if (!previousColumn) { @@ -930,7 +933,7 @@ export function canTransition({ Boolean(newColumn) && !newLayer.incompleteColumns?.[columnId] && filterOperations(newColumn) && - !newDefinition.getErrorMessage?.(newLayer, columnId, indexPattern)?.length + !newDefinition.getErrorMessage?.(newLayer, columnId, indexPattern, dateRange)?.length ); } catch (e) { return false; @@ -1574,7 +1577,14 @@ export function getErrorMessages( } const def = operationDefinitionMap[column.operationType]; if (def.getErrorMessage) { - return def.getErrorMessage(layer, columnId, indexPattern, operationDefinitionMap); + const currentTimeRange = data.query.timefilter.timefilter.getAbsoluteTime(); + return def.getErrorMessage( + layer, + columnId, + indexPattern, + { fromDate: currentTimeRange.from, toDate: currentTimeRange.to }, + operationDefinitionMap + ); } }) .map((errorMessage) => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/time_shift_utils.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/time_shift_utils.test.tsx index a0ff29a684150..3405752d3ec4f 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/time_shift_utils.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/time_shift_utils.test.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { getDisallowedPreviousShiftMessage } from './time_shift_utils'; +import moment from 'moment'; +import { getDisallowedPreviousShiftMessage, resolveTimeShift } from './time_shift_utils'; import { FormBasedLayer } from './types'; describe('time_shift_utils', () => { @@ -78,4 +79,51 @@ describe('time_shift_utils', () => { ).toBeUndefined(); }); }); + + describe('resolveTimeShift', () => { + const dateString = '2022-11-02T00:00:00.000Z'; + // shift by 2 days + 2500 s (to get a shift which is not a multiple of the given interval) + const shiftedDate = moment(dateString).subtract(175300, 's').toISOString(); + + function getDateRange(val = dateString) { + return { + fromDate: moment(val).subtract('1', 'd').toISOString(), + toDate: moment(val).add('1', 'd').toISOString(), + }; + } + + it('should not change a relative time shift', () => { + for (const val of ['1d', 'previous']) { + expect(resolveTimeShift(val, getDateRange(), 100)).toBe(val); + } + }); + + it('should change absolute values to relative in seconds (rounded) with start anchor', () => { + expect(resolveTimeShift(`startAt(${shiftedDate})`, getDateRange(), 100)) + // the raw value is 88900s, but that's not a multiple of the range interval + // so it will be rounded to the next interval multiple, then decremented by 1 interval unit (1800s) + // in order to include the provided date + .toBe('90000s'); + }); + + it('should change absolute values to relative in seconds (rounded) with end anchor', () => { + expect(resolveTimeShift(`endAt(${shiftedDate})`, getDateRange(), 100)) + // the raw value is 261700s, but that's not a multiple of the range interval + // so it will be rounded to the next interval multiple + .toBe('261000s'); + }); + + it('should always include the passed date in the computed interval', () => { + const dateRange = getDateRange(); + for (const anchor of ['startAt', 'endAt']) { + const [shift] = resolveTimeShift(`${anchor}(${shiftedDate})`, dateRange, 100)!.split('s'); + expect( + moment(shiftedDate).isBetween( + moment(dateRange.fromDate).subtract(Number(shift), 's'), + moment(dateRange.toDate).subtract(Number(shift), 's') + ) + ); + } + }); + }); }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/time_shift_utils.tsx b/x-pack/plugins/lens/public/datasources/form_based/time_shift_utils.tsx index 6dd4b88d3422a..b673f00a34392 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/time_shift_utils.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/time_shift_utils.tsx @@ -7,15 +7,28 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { uniq } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { DatatableUtilitiesService } from '@kbn/data-plugin/common'; +import moment from 'moment'; import { Datatable } from '@kbn/expressions-plugin/common'; import { search } from '@kbn/data-plugin/public'; -import { parseTimeShift } from '@kbn/data-plugin/common'; -import type { GenericIndexPatternColumn, FormBasedLayer, FormBasedPrivateState } from './types'; +import { + calcAutoIntervalNear, + DatatableUtilitiesService, + isAbsoluteTimeShift, + parseAbsoluteTimeShift, + parseTimeShift, +} from '@kbn/data-plugin/common'; +import type { DateRange } from '../../../common/types'; +import type { FormBasedLayer, FormBasedPrivateState } from './types'; import type { FramePublicAPI, IndexPattern } from '../../types'; +export function parseTimeShiftWrapper(timeShiftString: string, dateRange: DateRange) { + return isAbsoluteTimeShift(timeShiftString.trim()) + ? parseAbsoluteTimeShift(timeShiftString, { from: dateRange.fromDate, to: dateRange.toDate }) + .value + : parseTimeShift(timeShiftString); +} + export const timeShiftOptions = [ { label: i18n.translate('xpack.lens.indexPattern.timeShift.none', { @@ -134,7 +147,7 @@ export function getLayerTimeShiftChecks({ }: ReturnType) { return { canShift, - isValueTooSmall: (parsedValue: ReturnType) => { + isValueTooSmall: (parsedValue: ReturnType) => { return ( dateHistogramInterval && parsedValue && @@ -142,7 +155,7 @@ export function getLayerTimeShiftChecks({ parsedValue.asMilliseconds() < dateHistogramInterval.asMilliseconds() ); }, - isValueNotMultiple: (parsedValue: ReturnType) => { + isValueNotMultiple: (parsedValue: ReturnType) => { return ( dateHistogramInterval && parsedValue && @@ -150,7 +163,7 @@ export function getLayerTimeShiftChecks({ !Number.isInteger(parsedValue.asMilliseconds() / dateHistogramInterval.asMilliseconds()) ); }, - isInvalid: (parsedValue: ReturnType) => { + isInvalid: (parsedValue: ReturnType) => { return Boolean( parsedValue === 'invalid' || (hasDateHistogram && parsedValue && parsedValue === 'previous') ); @@ -164,7 +177,9 @@ export function getDisallowedPreviousShiftMessage( ): string[] | undefined { const currentColumn = layer.columns[columnId]; const hasPreviousShift = - currentColumn.timeShift && parseTimeShift(currentColumn.timeShift) === 'previous'; + currentColumn.timeShift && + !isAbsoluteTimeShift(currentColumn.timeShift) && + parseTimeShift(currentColumn.timeShift) === 'previous'; if (!hasPreviousShift) { return; } @@ -209,27 +224,28 @@ export function getStateTimeShiftWarningMessages( } const dateHistogramIntervalExpression = dateHistogramInterval.expression; const shiftInterval = dateHistogramInterval.interval.asMilliseconds(); - let timeShifts: number[] = []; + const timeShifts = new Set(); const timeShiftMap: Record = {}; Object.entries(layer.columns).forEach(([columnId, column]) => { if (column.isBucketed) return; let duration: number = 0; - if (column.timeShift) { + // skip absolute time shifts as underneath it will be converted to be round + // and avoid this type of issues + if (column.timeShift && !isAbsoluteTimeShift(column.timeShift)) { const parsedTimeShift = parseTimeShift(column.timeShift); if (parsedTimeShift === 'previous' || parsedTimeShift === 'invalid') { return; } duration = parsedTimeShift.asMilliseconds(); } - timeShifts.push(duration); + timeShifts.add(duration); if (!timeShiftMap[duration]) { timeShiftMap[duration] = []; } timeShiftMap[duration].push(columnId); }); - timeShifts = uniq(timeShifts); - if (timeShifts.length < 2) { + if (timeShifts.size < 2) { return; } @@ -273,13 +289,16 @@ export function getStateTimeShiftWarningMessages( export function getColumnTimeShiftWarnings( dateHistogramInterval: ReturnType, - column: GenericIndexPatternColumn + timeShift: string | undefined ) { const { isValueTooSmall, isValueNotMultiple } = getLayerTimeShiftChecks(dateHistogramInterval); const warnings: string[] = []; + if (isAbsoluteTimeShift(timeShift)) { + return warnings; + } - const parsedLocalValue = column.timeShift && parseTimeShift(column.timeShift); + const parsedLocalValue = timeShift && parseTimeShift(timeShift); const localValueTooSmall = parsedLocalValue && isValueTooSmall(parsedLocalValue); const localValueNotMultiple = parsedLocalValue && isValueNotMultiple(parsedLocalValue); if (localValueTooSmall) { @@ -299,3 +318,38 @@ export function getColumnTimeShiftWarnings( } return warnings; } + +function closestMultipleOfInterval(duration: number, interval: number) { + if (duration % interval === 0) { + return duration; + } + return Math.ceil(duration / interval) * interval; +} + +function roundAbsoluteInterval(timeShift: string, dateRange: DateRange, targetBars: number) { + // workout the interval (most probably matching the ES one) + const interval = calcAutoIntervalNear( + targetBars, + moment(dateRange.toDate).diff(moment(dateRange.fromDate)) + ); + const duration = parseTimeShiftWrapper(timeShift, dateRange); + if (typeof duration !== 'string') { + const roundingOffset = timeShift.startsWith('end') ? interval.asMilliseconds() : 0; + return `${ + (closestMultipleOfInterval(duration.asMilliseconds(), interval.asMilliseconds()) - + roundingOffset) / + 1000 + }s`; + } +} + +export function resolveTimeShift( + timeShift: string | undefined, + dateRange: DateRange, + targetBars: number +) { + if (timeShift && isAbsoluteTimeShift(timeShift)) { + return roundAbsoluteInterval(timeShift, dateRange, targetBars); + } + return timeShift; +} diff --git a/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts b/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts index d56d767084963..61e17c72ca829 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts @@ -8,7 +8,7 @@ import type { IUiSettingsClient } from '@kbn/core/public'; import { partition, uniq } from 'lodash'; import seedrandom from 'seedrandom'; -import { +import type { AggFunctionsMapping, EsaggsExpressionFunctionDefinition, IndexPatternLoadExpressionFunctionDefinition, @@ -21,6 +21,7 @@ import { ExpressionAstExpressionBuilder, ExpressionAstFunction, } from '@kbn/expressions-plugin/public'; +import type { DateRange } from '../../../common/types'; import { GenericIndexPatternColumn } from './form_based'; import { operationDefinitionMap } from './operations'; import { FormBasedPrivateState, FormBasedLayer } from './types'; @@ -29,6 +30,7 @@ import { FormattedIndexPatternColumn } from './operations/definitions/column_typ import { isColumnFormatted, isColumnOfType } from './operations/definitions/helpers'; import type { IndexPattern, IndexPatternMap } from '../../types'; import { dedupeAggs } from './dedupe_aggs'; +import { resolveTimeShift } from './time_shift_utils'; export type OriginalColumn = { id: string } & GenericIndexPatternColumn; @@ -54,6 +56,7 @@ function getExpressionForLayer( layer: FormBasedLayer, indexPattern: IndexPattern, uiSettings: IUiSettingsClient, + dateRange: DateRange, searchSessionId?: string ): ExpressionAstExpression | null { const { columnOrder } = layer; @@ -120,7 +123,10 @@ function getExpressionForLayer( operationDefinitionMap[col.operationType]?.input === 'fullReference' || operationDefinitionMap[col.operationType]?.input === 'managedReference' ); - const hasDateHistogram = columnEntries.some(([, c]) => c.operationType === 'date_histogram'); + const firstDateHistogramColumn = columnEntries.find( + ([, col]) => col.operationType === 'date_histogram' + ); + const hasDateHistogram = Boolean(firstDateHistogramColumn); if (referenceEntries.length || esAggEntries.length) { let aggs: ExpressionAstExpressionBuilder[] = []; @@ -137,6 +143,7 @@ function getExpressionForLayer( const orderedColumnIds = esAggEntries.map(([colId]) => colId); let esAggsIdMap: Record = {}; const aggExpressionToEsAggsIdMap: Map = new Map(); + const histogramBarsTarget = uiSettings.get('histogram:barTarget'); esAggEntries.forEach(([colId, col], index) => { const def = operationDefinitionMap[col.operationType]; if (def.input !== 'fullReference' && def.input !== 'managedReference') { @@ -149,7 +156,10 @@ function getExpressionForLayer( col.reducedTimeRange && indexPattern.timeFieldName; let aggAst = def.toEsAggsFn( - col, + { + ...col, + timeShift: resolveTimeShift(col.timeShift, dateRange, histogramBarsTarget), + }, wrapInFilter || wrapInTimeFilter ? `${aggId}-metric` : aggId, indexPattern, layer, @@ -171,11 +181,11 @@ function getExpressionForLayer( schema: 'bucket', filter: col.filter && queryToAst(col.filter), timeWindow: wrapInTimeFilter ? col.reducedTimeRange : undefined, - timeShift: col.timeShift, + timeShift: resolveTimeShift(col.timeShift, dateRange, histogramBarsTarget), }), ]), customMetric: buildExpression({ type: 'expression', chain: [aggAst] }), - timeShift: col.timeShift, + timeShift: resolveTimeShift(col.timeShift, dateRange, histogramBarsTarget), } ).toAst(); } @@ -310,10 +320,6 @@ function getExpressionForLayer( return base; }); - const firstDateHistogramColumn = columnEntries.find( - ([, col]) => col.operationType === 'date_histogram' - ); - const columnsWithTimeScale = columnEntries.filter( ([, col]) => col.timeScale && @@ -446,6 +452,7 @@ export function toExpression( layerId: string, indexPatterns: IndexPatternMap, uiSettings: IUiSettingsClient, + dateRange: DateRange, searchSessionId?: string ) { if (state.layers[layerId]) { @@ -453,6 +460,7 @@ export function toExpression( state.layers[layerId], indexPatterns[state.layers[layerId].indexPatternId], uiSettings, + dateRange, searchSessionId ); } diff --git a/x-pack/plugins/lens/public/datasources/form_based/utils.tsx b/x-pack/plugins/lens/public/datasources/form_based/utils.tsx index 19016b082ef5a..dee8dd71592ce 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/utils.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/utils.tsx @@ -25,6 +25,7 @@ import { } from '@kbn/data-plugin/public'; import { estypes } from '@elastic/elasticsearch'; +import type { DateRange } from '../../../common/types'; import type { FramePublicAPI, IndexPattern, StateSetter } from '../../types'; import { renewIDs } from '../../utils'; import type { FormBasedLayer, FormBasedPersistedState, FormBasedPrivateState } from './types'; @@ -54,7 +55,8 @@ import { isQueryValid } from '../../shared_components'; export function isColumnInvalid( layer: FormBasedLayer, columnId: string, - indexPattern: IndexPattern + indexPattern: IndexPattern, + dateRange: DateRange | undefined ) { const column: GenericIndexPatternColumn | undefined = layer.columns[columnId]; if (!column || !indexPattern) return; @@ -64,11 +66,17 @@ export function isColumnInvalid( const referencesHaveErrors = true && 'references' in column && - Boolean(getReferencesErrors(layer, column, indexPattern).filter(Boolean).length); + Boolean(getReferencesErrors(layer, column, indexPattern, dateRange).filter(Boolean).length); const operationErrorMessages = operationDefinition && - operationDefinition.getErrorMessage?.(layer, columnId, indexPattern, operationDefinitionMap); + operationDefinition.getErrorMessage?.( + layer, + columnId, + indexPattern, + dateRange, + operationDefinitionMap + ); const filterHasError = column.filter ? !isQueryValid(column.filter, indexPattern) : false; @@ -82,7 +90,8 @@ export function isColumnInvalid( function getReferencesErrors( layer: FormBasedLayer, column: ReferenceBasedIndexPatternColumn, - indexPattern: IndexPattern + indexPattern: IndexPattern, + dateRange: DateRange | undefined ) { return column.references?.map((referenceId: string) => { const referencedOperation = layer.columns[referenceId]?.operationType; @@ -91,6 +100,7 @@ function getReferencesErrors( layer, referenceId, indexPattern, + dateRange, operationDefinitionMap ); }); diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts index b7a99c41cf72a..5b7e4bbac36b9 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts @@ -76,6 +76,10 @@ const expectedIndexPatterns = { }; const indexPatterns = expectedIndexPatterns; +const dateRange = { + fromDate: '2022-03-17T08:25:00.000Z', + toDate: '2022-04-17T08:25:00.000Z', +}; describe('Textbased Data Source', () => { let baseState: TextBasedPrivateState; @@ -622,7 +626,9 @@ describe('Textbased Data Source', () => { describe('#toExpression', () => { it('should generate an empty expression when no columns are selected', async () => { const state = TextBasedDatasource.initialize(); - expect(TextBasedDatasource.toExpression(state, 'first', indexPatterns)).toEqual(null); + expect(TextBasedDatasource.toExpression(state, 'first', indexPatterns, dateRange)).toEqual( + null + ); }); it('should generate an expression for an SQL query', async () => { @@ -672,7 +678,7 @@ describe('Textbased Data Source', () => { ], } as unknown as TextBasedPrivateState; - expect(TextBasedDatasource.toExpression(queryBaseState, 'a', indexPatterns)) + expect(TextBasedDatasource.toExpression(queryBaseState, 'a', indexPatterns, dateRange)) .toMatchInlineSnapshot(` Object { "chain": Array [ diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 14f3ed9da1c89..03aed23f95237 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -566,7 +566,8 @@ export function LayerPanel( layerDatasourceState, dataViews.indexPatterns, layerId, - columnId + columnId, + dateRange ) } > diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts index aee10196d5156..7d2c642e21602 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts @@ -5,6 +5,7 @@ * 2.0. */ import { Ast, fromExpression } from '@kbn/interpreter'; +import type { DateRange } from '../../../common/types'; import { DatasourceStates } from '../../state_management'; import { Visualization, DatasourceMap, DatasourceLayers, IndexPatternMap } from '../../types'; @@ -12,6 +13,7 @@ export function getDatasourceExpressionsByLayers( datasourceMap: DatasourceMap, datasourceStates: DatasourceStates, indexPatterns: IndexPatternMap, + dateRange: DateRange, searchSessionId?: string ): null | Record { const datasourceExpressions: Array<[string, Ast | string]> = []; @@ -25,7 +27,13 @@ export function getDatasourceExpressionsByLayers( const layers = datasource.getLayers(state); layers.forEach((layerId) => { - const result = datasource.toExpression(state, layerId, indexPatterns, searchSessionId); + const result = datasource.toExpression( + state, + layerId, + indexPatterns, + dateRange, + searchSessionId + ); if (result) { datasourceExpressions.push([layerId, result]); } @@ -54,6 +62,7 @@ export function buildExpression({ title, description, indexPatterns, + dateRange, searchSessionId, }: { title?: string; @@ -65,6 +74,7 @@ export function buildExpression({ datasourceLayers: DatasourceLayers; indexPatterns: IndexPatternMap; searchSessionId?: string; + dateRange: DateRange; }): Ast | null { if (visualization === null) { return null; @@ -74,6 +84,7 @@ export function buildExpression({ datasourceMap, datasourceStates, indexPatterns, + dateRange, searchSessionId ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index e406417c2c2c2..8df771d8eb94b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -13,6 +13,7 @@ import { difference } from 'lodash'; import type { DataViewsContract, DataViewSpec } from '@kbn/data-views-plugin/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common'; +import type { TimefilterContract } from '@kbn/data-plugin/public'; import { Datasource, DatasourceLayers, @@ -321,6 +322,7 @@ export async function persistedStateToExpression( uiSettings: IUiSettingsClient; storage: IStorageWrapper; dataViews: DataViewsContract; + timefilter: TimefilterContract; } ): Promise<{ ast: Ast | null; errors: ErrorMessage[] | undefined }> { const { @@ -412,6 +414,7 @@ export async function persistedStateToExpression( visualizationState, { datasourceLayers, dataViews: { indexPatterns } as DataViewsState } ); + const currentTimeRange = services.timefilter.getAbsoluteTime(); return { ast: buildExpression({ @@ -423,6 +426,7 @@ export async function persistedStateToExpression( datasourceStates, datasourceLayers, indexPatterns, + dateRange: { fromDate: currentTimeRange.from, toDate: currentTimeRange.to }, }), errors: validationResult, }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 87b34e066f5e5..0e2cf398ab728 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -526,7 +526,8 @@ function getPreviewExpression( const datasourceExpressionsByLayers = getDatasourceExpressionsByLayers( datasources, datasourceStates, - frame.dataViews.indexPatterns + frame.dataViews.indexPatterns, + frame.dateRange ); return visualization.toPreviewExpression( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 36399211c92e3..8233eeef7079d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -310,7 +310,14 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ framePublicAPI ), // eslint-disable-next-line react-hooks/exhaustive-deps - [activeVisualization, visualization.state, activeDatasourceId, datasourceMap, datasourceStates] + [ + activeVisualization, + visualization.state, + activeDatasourceId, + datasourceMap, + datasourceStates, + framePublicAPI.dateRange, + ] ); // if the expression is undefined, it means we hit an error that should be displayed to the user @@ -324,6 +331,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ datasourceStates, datasourceLayers, indexPatterns: dataViews.indexPatterns, + dateRange: framePublicAPI.dateRange, searchSessionId, }); @@ -368,6 +376,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ datasourceLayers, dataViews.indexPatterns, searchSessionId, + framePublicAPI.dateRange, ]); useEffect(() => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 8c7e2378bd354..d375b75866cbb 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -14,6 +14,7 @@ import { DataPublicPluginSetup, DataPublicPluginStart, DataViewsContract, + TimefilterContract, } from '@kbn/data-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; @@ -55,6 +56,7 @@ export interface EditorFramePlugins { dataViews: DataViewsContract; uiSettings: IUiSettingsClient; storage: IStorageWrapper; + timefilter: TimefilterContract; } async function collectAsyncDefinitions( diff --git a/x-pack/plugins/lens/public/mocks/datasource_mock.ts b/x-pack/plugins/lens/public/mocks/datasource_mock.ts index caa074f145955..a9936a90c764f 100644 --- a/x-pack/plugins/lens/public/mocks/datasource_mock.ts +++ b/x-pack/plugins/lens/public/mocks/datasource_mock.ts @@ -42,7 +42,7 @@ export function createMockDatasource(id: string): DatasourceMock { initialize: jest.fn((_state?) => {}), renderDataPanel: jest.fn(), renderLayerPanel: jest.fn(), - toExpression: jest.fn((_frame, _state, _indexPatterns) => null), + toExpression: jest.fn((_frame, _state, _indexPatterns, dateRange) => null), insertLayer: jest.fn((_state, _newLayerId) => ({})), removeLayer: jest.fn((state, layerId) => ({ newState: state, removedLayerIds: [layerId] })), cloneLayer: jest.fn((_state, _layerId, _newLayerId, getNewId) => {}), diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index e5cba78d082c4..04c177237198f 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -300,6 +300,7 @@ export class LensPlugin { dataViews: plugins.dataViews, storage: new Storage(localStorage), uiSettings: core.uiSettings, + timefilter: plugins.data.query.timefilter.timefilter, }), injectFilterReferences: data.query.filterManager.inject.bind(data.query.filterManager), visualizationMap, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 4215b7a2ebae0..f2bb4226a2085 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -389,6 +389,7 @@ export interface Datasource { state: T, layerId: string, indexPatterns: IndexPatternMap, + dateRange: DateRange, searchSessionId?: string ) => ExpressionAstExpression | string | null; @@ -470,7 +471,8 @@ export interface Datasource { state: T, indexPatterns: IndexPatternMap, layerId: string, - columnId: string + columnId: string, + dateRange?: DateRange ) => boolean; /** * Are these datasources equivalent? @@ -611,6 +613,7 @@ export type DatasourceDimensionProps = SharedDimensionProps & { onRemove?: (accessor: string) => void; state: T; activeData?: Record; + dateRange: DateRange; indexPatterns: IndexPatternMap; hideTooltip?: boolean; invalid?: boolean; diff --git a/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts b/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts index 07cb2c0644476..589c0d5995784 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/to_expression.test.ts @@ -60,7 +60,8 @@ describe('#toExpression', () => { const datasourceExpression = mockDatasource.toExpression( frame.datasourceLayers.first, 'first', - frame.dataViews.indexPatterns + frame.dataViews.indexPatterns, + frame.dateRange ) ?? { type: 'expression', chain: [], diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index 5945ee3d35d8b..2e1ad348a47b1 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -8,6 +8,7 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "maps"], "requiredPlugins": [ + "controls", "unifiedSearch", "lens", "licensing", diff --git a/x-pack/plugins/maps/public/connected_components/map_container/index.ts b/x-pack/plugins/maps/public/connected_components/map_container/index.ts index 28671b13df5bc..f520f9cdc788e 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/index.ts +++ b/x-pack/plugins/maps/public/connected_components/map_container/index.ts @@ -9,7 +9,11 @@ import { AnyAction } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; import { connect } from 'react-redux'; import { MapContainer } from './map_container'; -import { getFlyoutDisplay, getIsFullScreen } from '../../selectors/ui_selectors'; +import { + getFlyoutDisplay, + getIsFullScreen, + getIsTimesliderOpen, +} from '../../selectors/ui_selectors'; import { cancelAllInFlightRequests, exitFullScreen } from '../../actions'; import { areLayersLoaded, @@ -22,6 +26,7 @@ import { MapStoreState } from '../../reducers/store'; function mapStateToProps(state: MapStoreState) { return { + isTimesliderOpen: getIsTimesliderOpen(state), areLayersLoaded: areLayersLoaded(state), flyoutDisplay: getFlyoutDisplay(state), isFullScreen: getIsFullScreen(state), diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index ddfaa99dd41c9..51c7b8675f145 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -13,7 +13,6 @@ import uuid from 'uuid/v4'; import { Filter } from '@kbn/es-query'; import { ActionExecutionContext, Action } from '@kbn/ui-actions-plugin/public'; import { Observable } from 'rxjs'; -import moment from 'moment'; import { ExitFullScreenButton } from '@kbn/shared-ux-button-exit-full-screen'; import { MBMap } from '../mb_map'; import { RightSideControls } from '../right_side_controls'; @@ -21,7 +20,7 @@ import { Timeslider } from '../timeslider'; import { ToolbarOverlay } from '../toolbar_overlay'; import { EditLayerPanel } from '../edit_layer_panel'; import { AddLayerPanel } from '../add_layer_panel'; -import { getData, isScreenshotMode } from '../../kibana_services'; +import { isScreenshotMode } from '../../kibana_services'; import { RawValue } from '../../../common/constants'; import { FLYOUT_STATE } from '../../reducers/ui'; import { MapSettings } from '../../../common/descriptor_types'; @@ -41,6 +40,7 @@ export interface Props { exitFullScreen: () => void; flyoutDisplay: FLYOUT_STATE; isFullScreen: boolean; + isTimesliderOpen: boolean; indexPatternIds: string[]; mapInitError: string | null | undefined; renderTooltipContent?: RenderToolTipContent; @@ -154,13 +154,6 @@ export class MapContainer extends Component { }, 5000); }; - _updateGlobalTimeRange(data: number[]) { - getData().query.timefilter.timefilter.setTime({ - from: moment(data[0]).toISOString(), - to: moment(data[1]).toISOString(), - }); - } - render() { const { addFilters, @@ -241,13 +234,10 @@ export class MapContainer extends Component { /> )} + {this.props.isTimesliderOpen && ( + + )} - - - ) { return { - closeTimeslider: () => { - dispatch(closeTimeslider()); - }, - setTimeslice: (timeslice: Timeslice) => { + setTimeslice: (timeslice?: Timeslice) => { dispatch( setQuery({ forceRefresh: false, diff --git a/x-pack/plugins/maps/public/connected_components/timeslider/time_utils.test.ts b/x-pack/plugins/maps/public/connected_components/timeslider/time_utils.test.ts deleted file mode 100644 index 16973b5a84478..0000000000000 --- a/x-pack/plugins/maps/public/connected_components/timeslider/time_utils.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getInterval } from './time_utils'; - -describe('getInterval', () => { - test('should provide interval of 1 day for 7 day range', () => { - expect(getInterval(1617630946622, 1618235746622)).toBe(86400000); - }); - - test('should provide interval of 3 hours for 24 hour range', () => { - expect(getInterval(1618150382531, 1618236782531)).toBe(10800000); - }); - - test('should provide interval of 90 minues for 12 hour range', () => { - expect(getInterval(1618193892632, 1618237092632)).toBe(5400000); - }); - - test('should provide interval of 30 minues for 4 hour range', () => { - expect(getInterval(1618222509189, 1618236909189)).toBe(1800000); - }); - - test('should provide interval of 10 minues for 1 hour range', () => { - expect(getInterval(1618233266459, 1618236866459)).toBe(600000); - }); -}); diff --git a/x-pack/plugins/maps/public/connected_components/timeslider/time_utils.ts b/x-pack/plugins/maps/public/connected_components/timeslider/time_utils.ts deleted file mode 100644 index 01f79114b13d0..0000000000000 --- a/x-pack/plugins/maps/public/connected_components/timeslider/time_utils.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import moment from 'moment-timezone'; -import { EuiRangeTick } from '@elastic/eui/src/components/form/range/range_ticks'; -import { calcAutoIntervalNear } from '@kbn/data-plugin/common'; -import { getUiSettings } from '../../kibana_services'; - -function getTimezone() { - const detectedTimezone = moment.tz.guess(); - const dateFormatTZ = getUiSettings().get('dateFormat:tz', 'Browser'); - - return dateFormatTZ === 'Browser' ? detectedTimezone : dateFormatTZ; -} - -function getScaledDateFormat(interval: number): string { - if (interval >= moment.duration(1, 'y').asMilliseconds()) { - return 'YYYY'; - } - - if (interval >= moment.duration(1, 'd').asMilliseconds()) { - return 'MMM D'; - } - - if (interval >= moment.duration(6, 'h').asMilliseconds()) { - return 'Do HH'; - } - - if (interval >= moment.duration(1, 'h').asMilliseconds()) { - return 'HH:mm'; - } - - if (interval >= moment.duration(1, 'm').asMilliseconds()) { - return 'HH:mm'; - } - - if (interval >= moment.duration(1, 's').asMilliseconds()) { - return 'mm:ss'; - } - - return 'ss.SSS'; -} - -export function epochToKbnDateFormat(epoch: number): string { - const dateFormat = getUiSettings().get('dateFormat', 'MMM D, YYYY @ HH:mm:ss.SSS'); - const timezone = getTimezone(); - return moment.tz(epoch, timezone).format(dateFormat); -} - -export function getInterval(min: number, max: number, steps = 6): number { - const duration = max - min; - let interval = calcAutoIntervalNear(steps, duration).asMilliseconds(); - // Sometimes auto interval is not quite right and returns 2X or 3X requested ticks - // Adjust the interval to get closer to the requested number of ticks - const actualSteps = duration / interval; - if (actualSteps > steps * 1.5) { - const factor = Math.round(actualSteps / steps); - interval *= factor; - } else if (actualSteps < 5) { - interval *= 0.5; - } - return interval; -} - -export function getTicks(min: number, max: number, interval: number): EuiRangeTick[] { - const format = getScaledDateFormat(interval); - const timezone = getTimezone(); - - let tick = Math.ceil(min / interval) * interval; - const ticks: EuiRangeTick[] = []; - while (tick < max) { - ticks.push({ - value: tick, - label: moment.tz(tick, timezone).format(format), - }); - tick += interval; - } - - return ticks; -} diff --git a/x-pack/plugins/maps/public/connected_components/timeslider/timeslider.tsx b/x-pack/plugins/maps/public/connected_components/timeslider/timeslider.tsx index d2ed9d640b140..75e474cc064fc 100644 --- a/x-pack/plugins/maps/public/connected_components/timeslider/timeslider.tsx +++ b/x-pack/plugins/maps/public/connected_components/timeslider/timeslider.tsx @@ -7,248 +7,105 @@ import _ from 'lodash'; import React, { Component } from 'react'; -import { EuiButtonIcon, EuiDualRange, EuiText } from '@elastic/eui'; -import { EuiRangeTick } from '@elastic/eui/src/components/form/range/range_ticks'; -import { i18n } from '@kbn/i18n'; import { Observable, Subscription } from 'rxjs'; +import { distinctUntilChanged } from 'rxjs/operators'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { + ControlGroupContainer, + type ControlGroupInput, + type controlGroupInputBuilder, + LazyControlGroupRenderer, +} from '@kbn/controls-plugin/public'; +import { withSuspense } from '@kbn/presentation-util-plugin/public'; import { first } from 'rxjs/operators'; import type { TimeRange } from '@kbn/es-query'; -import { epochToKbnDateFormat, getInterval, getTicks } from './time_utils'; -import { getTimeFilter } from '../../kibana_services'; import { Timeslice } from '../../../common/descriptor_types'; +const ControlGroupRenderer = withSuspense(LazyControlGroupRenderer); + export interface Props { - closeTimeslider: () => void; - setTimeslice: (timeslice: Timeslice) => void; - isTimesliderOpen: boolean; + setTimeslice: (timeslice?: Timeslice) => void; timeRange: TimeRange; waitForTimesliceToLoad$: Observable; - updateGlobalTimeRange: (timeslice: number[]) => void; -} - -interface State { - isPaused: boolean; - max: number; - min: number; - range: number; - timeslice: [number, number]; - ticks: EuiRangeTick[]; -} - -function prettyPrintTimeslice(timeslice: [number, number]) { - return `${epochToKbnDateFormat(timeslice[0])} - ${epochToKbnDateFormat(timeslice[1])}`; } -// Why Timeslider and KeyedTimeslider? -// Using react 'key' property to ensure new KeyedTimeslider instance whenever props.timeRange changes -export function Timeslider(props: Props) { - return props.isTimesliderOpen ? ( - - ) : null; -} - -class KeyedTimeslider extends Component { +export class Timeslider extends Component { private _isMounted: boolean = false; - private _timeoutId: number | undefined; - private _subscription: Subscription | undefined; - - constructor(props: Props) { - super(props); - const timeRangeBounds = getTimeFilter().calculateBounds(props.timeRange); - if (timeRangeBounds.min === undefined || timeRangeBounds.max === undefined) { - throw new Error( - 'Unable to create Timeslider component, timeRangeBounds min or max are undefined' - ); - } - const min = timeRangeBounds.min.valueOf(); - const max = timeRangeBounds.max.valueOf(); - const interval = getInterval(min, max); - const timeslice: [number, number] = [min, max]; - - this.state = { - isPaused: true, - max, - min, - range: interval, - ticks: getTicks(min, max, interval), - timeslice, - }; - } + private _controlGroup?: ControlGroupContainer | undefined; + private readonly _subscriptions = new Subscription(); componentWillUnmount() { - this._onPause(); this._isMounted = false; + this._subscriptions.unsubscribe(); } - componentDidMount() { - this._isMounted = true; - // auto-select range between first tick and second tick - this._onChange([this.state.ticks[0].value, this.state.ticks[1].value]); + componentDidUpdate() { + if ( + this._controlGroup && + !_.isEqual(this._controlGroup.getInput().timeRange, this.props.timeRange) + ) { + this._controlGroup.updateInput({ + timeRange: this.props.timeRange, + }); + } } - _doesTimesliceCoverTimerange() { - return this.state.timeslice[0] === this.state.min && this.state.timeslice[1] === this.state.max; + componentDidMount() { + this._isMounted = true; } - _onDualControlChange = (value: [number | string, number | string]) => { - this.setState({ range: (value[1] as number) - (value[0] as number) }, () => { - this._onChange(value as [number, number]); - }); - }; - - _onChange = (value: [number, number]) => { - this.setState({ - timeslice: value, - }); - this._propagateChange(value); - }; - - _onNext = () => { - const from = - this._doesTimesliceCoverTimerange() || this.state.timeslice[1] === this.state.max - ? this.state.ticks[0].value - : this.state.timeslice[1]; - const to = from + this.state.range; - this._onChange([from, to <= this.state.max ? to : this.state.max]); - }; - - _onPrevious = () => { - const to = - this._doesTimesliceCoverTimerange() || this.state.timeslice[0] === this.state.min - ? this.state.ticks[this.state.ticks.length - 1].value - : this.state.timeslice[0]; - const from = to - this.state.range; - this._onChange([from < this.state.min ? this.state.min : from, to]); + _getInitialInput = async ( + initialInput: Partial, + builder: typeof controlGroupInputBuilder + ) => { + builder.addTimeSliderControl(initialInput); + return { + ...initialInput, + viewMode: ViewMode.VIEW, + timeRange: this.props.timeRange, + }; }; - _propagateChange = _.debounce((value: [number, number]) => { - if (this._isMounted) { - this.props.setTimeslice({ from: value[0], to: value[1] }); + _onLoadComplete = (controlGroup: ControlGroupContainer) => { + if (!this._isMounted) { + return; } - }, 300); - - _onPlay = () => { - this.setState({ isPaused: false }); - this._playNextFrame(); - }; - _onPause = () => { - this.setState({ isPaused: true }); - if (this._subscription) { - this._subscription.unsubscribe(); - this._subscription = undefined; - } - if (this._timeoutId) { - clearTimeout(this._timeoutId); - this._timeoutId = undefined; - } + this._controlGroup = controlGroup; + this._subscriptions.add( + this._controlGroup + .getOutput$() + .pipe( + distinctUntilChanged(({ timeslice: timesliceA }, { timeslice: timesliceB }) => + _.isEqual(timesliceA, timesliceB) + ) + ) + .subscribe(({ timeslice }) => { + // use waitForTimesliceToLoad$ observable to wait until next frame loaded + // .pipe(first()) waits until the first value is emitted from an observable and then automatically unsubscribes + this.props.waitForTimesliceToLoad$.pipe(first()).subscribe(() => { + this._controlGroup!.anyControlOutputConsumerLoading$.next(false); + }); + + this.props.setTimeslice( + timeslice === undefined + ? undefined + : { + from: timeslice[0], + to: timeslice[1], + } + ); + }) + ); }; - _playNextFrame() { - // advance to next frame - this._onNext(); - - // use waitForTimesliceToLoad$ observable to wait until next frame loaded - // .pipe(first()) waits until the first value is emitted from an observable and then automatically unsubscribes - this._subscription = this.props.waitForTimesliceToLoad$.pipe(first()).subscribe(() => { - if (this.state.isPaused) { - return; - } - - // use timeout to display frame for small time period before moving to next frame - this._timeoutId = window.setTimeout(() => { - if (this.state.isPaused) { - return; - } - this._playNextFrame(); - }, 1750); - }); - } - render() { return (
-
- - -
- {prettyPrintTimeslice(this.state.timeslice)} -
- - { - this.props.updateGlobalTimeRange(this.state.timeslice); - }} - iconType="calendar" - aria-label={i18n.translate('xpack.maps.timeslider.setGlobalTime', { - defaultMessage: 'Set global time to {timeslice}', - values: { timeslice: prettyPrintTimeslice(this.state.timeslice) }, - })} - title={i18n.translate('xpack.maps.timeslider.setGlobalTime', { - defaultMessage: 'Set global time to {timeslice}', - values: { timeslice: prettyPrintTimeslice(this.state.timeslice) }, - })} - /> - -
-
- - - -
-
-
- -
- -
+
); } diff --git a/x-pack/plugins/ml/common/constants/multi_bucket_impact.ts b/x-pack/plugins/ml/common/constants/multi_bucket_impact.ts deleted file mode 100644 index c708f15862163..0000000000000 --- a/x-pack/plugins/ml/common/constants/multi_bucket_impact.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/** - * Thresholds for indicating the impact of multi-bucket features in an anomaly. - * As a rule-of-thumb, a threshold value T corresponds to the multi-bucket probability - * being 1000^(T/5) times smaller than the single bucket probability. - * So, for example, for HIGH it is 63 times smaller. - */ -export const MULTI_BUCKET_IMPACT = { - HIGH: 3, - MEDIUM: 2, - LOW: 1, - NONE: -5, -}; diff --git a/x-pack/plugins/ml/common/types/results.ts b/x-pack/plugins/ml/common/types/results.ts index 9997bc4751b41..cf25fc6081e16 100644 --- a/x-pack/plugins/ml/common/types/results.ts +++ b/x-pack/plugins/ml/common/types/results.ts @@ -99,6 +99,7 @@ export interface ChartPoint { anomalyScore?: number; actual?: number[]; multiBucketImpact?: number; + isMultiBucketAnomaly?: boolean; typical?: number[]; value?: number | null; entity?: string; diff --git a/x-pack/plugins/ml/common/util/anomaly_utils.test.ts b/x-pack/plugins/ml/common/util/anomaly_utils.test.ts index 24eb06d8bd053..b707a61d5b9bc 100644 --- a/x-pack/plugins/ml/common/util/anomaly_utils.test.ts +++ b/x-pack/plugins/ml/common/util/anomaly_utils.test.ts @@ -12,13 +12,13 @@ import { getEntityFieldList, getEntityFieldName, getEntityFieldValue, - getMultiBucketImpactLabel, getSeverity, getSeverityWithLow, getSeverityColor, isRuleSupported, showActualForFunction, showTypicalForFunction, + isMultiBucketAnomaly, } from './anomaly_utils'; describe('ML - anomaly utils', () => { @@ -260,30 +260,152 @@ describe('ML - anomaly utils', () => { }); }); - describe('getMultiBucketImpactLabel', () => { - test('returns high for 3 <= score <= 5', () => { - expect(getMultiBucketImpactLabel(3)).toBe('high'); - expect(getMultiBucketImpactLabel(5)).toBe('high'); + describe('isMultiBucketAnomaly', () => { + const singleBucketAnomaly: AnomalyRecordDoc = { + job_id: 'farequote_sb', + result_type: 'record', + probability: 0.0191711, + record_score: 4.38431, + initial_record_score: 19.654, + bucket_span: 300, + detector_index: 0, + is_interim: false, + timestamp: 1454890500000, + function: 'mean', + function_description: 'mean', + field_name: 'responsetime', + anomaly_score_explanation: { + single_bucket_impact: 65, + multi_bucket_impact: 14, + lower_confidence_bound: 94.79879269994528, + typical_value: 100.26620234643129, + upper_confidence_bound: 106.04564690901603, + }, + }; + + const multiBucketAnomaly: AnomalyRecordDoc = { + job_id: 'farequote_mb', + result_type: 'record', + probability: 0.0191711, + record_score: 4.38431, + initial_record_score: 19.654, + bucket_span: 300, + detector_index: 0, + is_interim: false, + timestamp: 1454890500000, + function: 'mean', + function_description: 'mean', + field_name: 'responsetime', + anomaly_score_explanation: { + single_bucket_impact: 14, + multi_bucket_impact: 65, + lower_confidence_bound: 94.79879269994528, + typical_value: 100.26620234643129, + upper_confidence_bound: 106.04564690901603, + }, + }; + + const multiBucketAnomaly2: AnomalyRecordDoc = { + job_id: 'farequote_mb2', + result_type: 'record', + probability: 0.0191711, + record_score: 4.38431, + initial_record_score: 19.654, + bucket_span: 300, + detector_index: 0, + is_interim: false, + timestamp: 1454890500000, + function: 'mean', + function_description: 'mean', + field_name: 'responsetime', + anomaly_score_explanation: { + multi_bucket_impact: 65, + lower_confidence_bound: 94.79879269994528, + typical_value: 100.26620234643129, + upper_confidence_bound: 106.04564690901603, + }, + }; + + const noASEAnomaly: AnomalyRecordDoc = { + job_id: 'farequote_ase', + result_type: 'record', + probability: 0.0191711, + record_score: 4.38431, + initial_record_score: 19.654, + bucket_span: 300, + detector_index: 0, + is_interim: false, + timestamp: 1454890500000, + function: 'mean', + function_description: 'mean', + field_name: 'responsetime', + }; + + const noMBIAnomaly: AnomalyRecordDoc = { + job_id: 'farequote_sbi', + result_type: 'record', + probability: 0.0191711, + record_score: 4.38431, + initial_record_score: 19.654, + bucket_span: 300, + detector_index: 0, + is_interim: false, + timestamp: 1454890500000, + function: 'mean', + function_description: 'mean', + field_name: 'responsetime', + anomaly_score_explanation: { + single_bucket_impact: 65, + lower_confidence_bound: 94.79879269994528, + typical_value: 100.26620234643129, + upper_confidence_bound: 106.04564690901603, + }, + }; + + const singleBucketAnomaly2: AnomalyRecordDoc = { + job_id: 'farequote_sb2', + result_type: 'record', + probability: 0.0191711, + record_score: 4.38431, + initial_record_score: 19.654, + bucket_span: 300, + detector_index: 0, + is_interim: false, + timestamp: 1454890500000, + function: 'mean', + function_description: 'mean', + field_name: 'responsetime', + anomaly_score_explanation: { + single_bucket_impact: 65, + multi_bucket_impact: 65, + lower_confidence_bound: 94.79879269994528, + typical_value: 100.26620234643129, + upper_confidence_bound: 106.04564690901603, + }, + }; + + test('returns false when single_bucket_impact much larger than multi_bucket_impact', () => { + expect(isMultiBucketAnomaly(singleBucketAnomaly)).toBe(false); + }); + + test('returns true when multi_bucket_impact much larger than single_bucket_impact', () => { + expect(isMultiBucketAnomaly(multiBucketAnomaly)).toBe(true); }); - test('returns medium for 2 <= score < 3', () => { - expect(getMultiBucketImpactLabel(2)).toBe('medium'); - expect(getMultiBucketImpactLabel(2.99)).toBe('medium'); + test('returns true when multi_bucket_impact > 0 and single_bucket_impact undefined', () => { + expect(isMultiBucketAnomaly(multiBucketAnomaly2)).toBe(true); }); - test('returns low for 1 <= score < 2', () => { - expect(getMultiBucketImpactLabel(1)).toBe('low'); - expect(getMultiBucketImpactLabel(1.99)).toBe('low'); + test('returns false when anomaly_score_explanation undefined', () => { + expect(isMultiBucketAnomaly(noASEAnomaly)).toBe(false); }); - test('returns none for -5 <= score < 1', () => { - expect(getMultiBucketImpactLabel(-5)).toBe('none'); - expect(getMultiBucketImpactLabel(0.99)).toBe('none'); + test('returns false when multi_bucket_impact undefined', () => { + expect(isMultiBucketAnomaly(noMBIAnomaly)).toBe(false); }); - test('returns expected label when impact outside normal bounds', () => { - expect(getMultiBucketImpactLabel(10)).toBe('high'); - expect(getMultiBucketImpactLabel(-10)).toBe('none'); + test('returns false when multi_bucket_impact === single_bucket_impact', () => { + expect(isMultiBucketAnomaly(singleBucketAnomaly2)).toBe(false); }); }); diff --git a/x-pack/plugins/ml/common/util/anomaly_utils.ts b/x-pack/plugins/ml/common/util/anomaly_utils.ts index cbbec963b0c3d..31a2a5fe49ca5 100644 --- a/x-pack/plugins/ml/common/util/anomaly_utils.ts +++ b/x-pack/plugins/ml/common/util/anomaly_utils.ts @@ -12,7 +12,6 @@ import { i18n } from '@kbn/i18n'; import { CONDITIONS_NOT_SUPPORTED_FUNCTIONS } from '../constants/detector_rule'; -import { MULTI_BUCKET_IMPACT } from '../constants/multi_bucket_impact'; import { ANOMALY_SEVERITY, ANOMALY_THRESHOLD, SEVERITY_COLORS } from '../constants/anomalies'; import type { AnomaliesTableRecord, AnomalyRecordDoc } from '../types/anomalies'; @@ -119,6 +118,10 @@ function getSeverityTypes() { }); } +/** + * Returns whether the anomaly is in a categorization analysis. + * @param anomaly Anomaly table record + */ export function isCategorizationAnomaly(anomaly: AnomaliesTableRecord): boolean { return anomaly.entityName === 'mlcategory'; } @@ -219,29 +222,54 @@ export function getSeverityColor(normalizedScore: number): string { } /** - * Returns a label to use for the multi-bucket impact of an anomaly - * according to the value of the multi_bucket_impact field of a record, - * which ranges from -5 to +5. - * @param multiBucketImpact - Value of the multi_bucket_impact field of a record, from -5 to +5 + * Returns whether the anomaly record should be indicated in the UI as a multi-bucket anomaly, + * for example in anomaly charts with a cross-shaped marker. + * @param anomaly Anomaly table record */ -export function getMultiBucketImpactLabel(multiBucketImpact: number): string { - if (multiBucketImpact >= MULTI_BUCKET_IMPACT.HIGH) { - return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.highLabel', { - defaultMessage: 'high', - }); - } else if (multiBucketImpact >= MULTI_BUCKET_IMPACT.MEDIUM) { - return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.mediumLabel', { - defaultMessage: 'medium', - }); - } else if (multiBucketImpact >= MULTI_BUCKET_IMPACT.LOW) { - return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.lowLabel', { - defaultMessage: 'low', - }); - } else { - return i18n.translate('xpack.ml.anomalyUtils.multiBucketImpact.noneLabel', { - defaultMessage: 'none', - }); +export function isMultiBucketAnomaly(anomaly: AnomalyRecordDoc): boolean { + if (anomaly.anomaly_score_explanation === undefined) { + return false; } + + const sb = anomaly.anomaly_score_explanation.single_bucket_impact; + const mb = anomaly.anomaly_score_explanation.multi_bucket_impact; + + if (mb === undefined || mb === 0) { + return false; + } + + if (sb !== undefined && sb > mb) { + return false; + } + + if ((sb === undefined || sb === 0) && mb > 0) { + return true; + } + + // Basis of use of 1.7 comes from the backend calculation for + // the single- and multi-bucket impacts + // 1.7 = 5.0/lg(e)/ln(1000) + // with the computation of the logarithm basis changed from e to 10. + if (sb !== undefined && mb > sb) { + return (((mb - sb) * mb) / sb) * 1.7 >= 2; + } + + return false; +} + +/** + * Returns the value on a scale of 1 to 5, from a log based scaled value for an + * anomaly score explanation impact field, such as anomaly_characteristics_impact, + * single_bucket_impact or multi_bucket_impact. + * @param score value from an impact field from the anomaly_score_explanation. + * @returns numeric value on an integer scale of 1 (low) to 5 (high). + */ +export function getAnomalyScoreExplanationImpactValue(score: number): number { + if (score < 2) return 1; + if (score < 4) return 2; + if (score < 7) return 3; + if (score < 12) return 4; + return 5; } /** diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js index 07f52b03e6221..c8381726ee86a 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js @@ -27,7 +27,7 @@ import { InfluencersCell } from './influencers_cell'; import { LinksMenu } from './links_menu'; import { checkPermission } from '../../capabilities/check_capabilities'; import { mlFieldFormatService } from '../../services/field_format_service'; -import { isRuleSupported } from '../../../../common/util/anomaly_utils'; +import { isRuleSupported, isMultiBucketAnomaly } from '../../../../common/util/anomaly_utils'; import { formatValue } from '../../formatters/format_value'; import { INFLUENCERS_LIMIT, ANOMALIES_TABLE_TABS } from './anomalies_table_constants'; import { SeverityCell } from './severity_cell'; @@ -133,7 +133,7 @@ export function getColumns( ), render: (score, item) => ( - + ), sortable: true, }, diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx index a049ee2c48ea6..0254c27f67906 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx @@ -26,7 +26,10 @@ import { import { AnomaliesTableRecord, MLAnomalyDoc } from '../../../../common/types/anomalies'; import { formatValue } from '../../formatters/format_value'; import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; -import { getSeverityColor } from '../../../../common/util/anomaly_utils'; +import { + getAnomalyScoreExplanationImpactValue, + getSeverityColor, +} from '../../../../common/util/anomaly_utils'; const TIME_FIELD_NAME = 'timestamp'; @@ -597,14 +600,6 @@ function getAnomalyType(explanation: MLAnomalyDoc['anomaly_score_explanation']) return explanation.anomaly_type === 'dip' ? dip : spike; } -function getImpactValue(score: number) { - if (score < 2) return 1; - if (score < 4) return 2; - if (score < 7) return 3; - if (score < 12) return 4; - return 5; -} - const impactTooltips = { anomaly_characteristics: { low: i18n.translate( @@ -681,7 +676,7 @@ function getImpactTooltip( score: number, type: 'anomaly_characteristics' | 'single_bucket' | 'multi_bucket' ) { - const value = getImpactValue(score); + const value = getAnomalyScoreExplanationImpactValue(score); if (value < 3) { return impactTooltips[type].low; @@ -698,7 +693,7 @@ const ImpactVisual: FC<{ score: number }> = ({ score }) => { euiTheme: { colors }, } = useEuiTheme(); - const impact = getImpactValue(score); + const impact = getAnomalyScoreExplanationImpactValue(score); const boxPx = '10px'; const emptyBox = colors.lightShade; const fullBox = colors.primary; diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx index f58fe20facbf6..e9166c0c6c28c 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx @@ -13,7 +13,7 @@ describe('SeverityCell', () => { test('should render a single-bucket marker with rounded severity score', () => { const props = { score: 75.2, - multiBucketImpact: -2, + isMultiBucketAnomaly: false, }; const { container } = render(); expect(container.textContent).toBe('75'); @@ -24,7 +24,7 @@ describe('SeverityCell', () => { test('should render a multi-bucket marker with low severity score', () => { const props = { score: 0.8, - multiBucketImpact: 4, + isMultiBucketAnomaly: true, }; const { container } = render(); expect(container.textContent).toBe('< 1'); diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.tsx index b761599a447b7..555110e4a3e4d 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.tsx +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.tsx @@ -7,7 +7,6 @@ import React, { FC, memo } from 'react'; import { EuiHealth, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { MULTI_BUCKET_IMPACT } from '../../../../../common/constants/multi_bucket_impact'; import { getSeverityColor, getFormattedSeverityScore, @@ -19,21 +18,19 @@ interface SeverityCellProps { */ score: number; /** - * Multi-bucket impact score from –5 to 5. - * Anomalies with a multi-bucket impact value of greater than or equal - * to 2 are indicated with a plus shaped symbol in the cell. + * Flag to indicate whether the anomaly should be displayed in the cell as a + * multi-bucket anomaly with a plus-shaped symbol. */ - multiBucketImpact: number; + isMultiBucketAnomaly: boolean; } /** * Renders anomaly severity score with single or multi-bucket impact marker. */ -export const SeverityCell: FC = memo(({ score, multiBucketImpact }) => { +export const SeverityCell: FC = memo(({ score, isMultiBucketAnomaly }) => { const severity = getFormattedSeverityScore(score); const color = getSeverityColor(score); - const isMultiBucket = multiBucketImpact >= MULTI_BUCKET_IMPACT.MEDIUM; - return isMultiBucket ? ( + return isMultiBucketAnomaly ? ( diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js index 6ce196393b97e..23f1b91910887 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js @@ -24,7 +24,6 @@ import { getFormattedSeverityScore, getSeverityColor, getSeverityWithLow, - getMultiBucketImpactLabel, } from '../../../../common/util/anomaly_utils'; import { LINE_CHART_ANOMALY_RADIUS, @@ -36,6 +35,7 @@ import { removeLabelOverlap, showMultiBucketAnomalyMarker, showMultiBucketAnomalyTooltip, + getMultiBucketImpactTooltipValue, } from '../../util/chart_utils'; import { LoadingIndicator } from '../../components/loading_indicator/loading_indicator'; import { mlFieldFormatService } from '../../services/field_format_service'; @@ -461,6 +461,7 @@ export class ExplorerChartSingleMetric extends React.Component { if (marker.anomalyScore !== undefined) { const score = parseInt(marker.anomalyScore); + tooltipData.push({ label: i18n.translate('xpack.ml.explorer.singleMetricChart.anomalyScoreLabel', { defaultMessage: 'anomaly score', @@ -478,7 +479,7 @@ export class ExplorerChartSingleMetric extends React.Component { label: i18n.translate('xpack.ml.explorer.singleMetricChart.multiBucketImpactLabel', { defaultMessage: 'multi-bucket impact', }), - value: getMultiBucketImpactLabel(marker.multiBucketImpact), + value: getMultiBucketImpactTooltipValue(marker), seriesIdentifier: { key: seriesKey, }, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index 8daf2e8f86891..707511c1f22d4 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -21,7 +21,6 @@ import { i18n } from '@kbn/i18n'; import { getFormattedSeverityScore, getSeverityWithLow, - getMultiBucketImpactLabel, } from '../../../../../common/util/anomaly_utils'; import { formatValue } from '../../../formatters/format_value'; import { @@ -34,6 +33,7 @@ import { numTicksForDateFormat, showMultiBucketAnomalyMarker, showMultiBucketAnomalyTooltip, + getMultiBucketImpactTooltipValue, } from '../../../util/chart_utils'; import { formatHumanReadableDateTimeSeconds } from '../../../../../common/util/date_utils'; import { getTimeBucketsFromCache } from '../../../util/time_buckets'; @@ -1509,12 +1509,12 @@ class TimeseriesChartIntl extends Component { if (showMultiBucketAnomalyTooltip(marker) === true) { tooltipData.push({ label: i18n.translate( - 'xpack.ml.timeSeriesExplorer.timeSeriesChart.multiBucketImpactLabel', + 'xpack.ml.timeSeriesExplorer.timeSeriesChart.multiBucketAnomalyLabel', { defaultMessage: 'multi-bucket impact', } ), - value: getMultiBucketImpactLabel(marker.multiBucketImpact), + value: getMultiBucketImpactTooltipValue(marker), seriesIdentifier: { key: seriesKey, }, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_help_popover.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_help_popover.tsx index 4313c9b228825..afd93fd5acee1 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_help_popover.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_help_popover.tsx @@ -27,7 +27,7 @@ export const TimeSeriesExplorerHelpPopover = () => {

diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js index 5a508cbf4bb41..cd7c12e5c3430 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js @@ -14,6 +14,7 @@ import { each, get, find } from 'lodash'; import moment from 'moment-timezone'; +import { isMultiBucketAnomaly } from '../../../../common/util/anomaly_utils'; import { isTimeSeriesViewJob } from '../../../../common/util/job_utils'; import { parseInterval } from '../../../../common/util/parse_interval'; @@ -193,9 +194,14 @@ export function processDataForFocusAnomalies( } } - if (record.multi_bucket_impact !== undefined) { - chartPoint.multiBucketImpact = record.multi_bucket_impact; + if ( + record.anomaly_score_explanation !== undefined && + record.anomaly_score_explanation.multi_bucket_impact !== undefined + ) { + chartPoint.multiBucketImpact = record.anomaly_score_explanation.multi_bucket_impact; } + + chartPoint.isMultiBucketAnomaly = isMultiBucketAnomaly(record); } } }); diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.js b/x-pack/plugins/ml/public/application/util/chart_utils.js index 4936f80f1911c..ab842fe6f688d 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.js +++ b/x-pack/plugins/ml/public/application/util/chart_utils.js @@ -7,7 +7,7 @@ import d3 from 'd3'; import { calculateTextWidth } from './string_utils'; -import { MULTI_BUCKET_IMPACT } from '../../../common/constants/multi_bucket_impact'; +import { getAnomalyScoreExplanationImpactValue } from '../../../common/util/anomaly_utils'; import moment from 'moment'; import { CHART_TYPE } from '../explorer/explorer_constants'; import { ML_PAGES } from '../../../common/constants/locator'; @@ -224,17 +224,22 @@ export async function getExploreSeriesLink(mlLocator, series, timeRange) { } export function showMultiBucketAnomalyMarker(point) { - // TODO - test threshold with real use cases - return ( - point.multiBucketImpact !== undefined && point.multiBucketImpact >= MULTI_BUCKET_IMPACT.MEDIUM - ); + return point.isMultiBucketAnomaly === true; } export function showMultiBucketAnomalyTooltip(point) { - // TODO - test threshold with real use cases - return ( - point.multiBucketImpact !== undefined && point.multiBucketImpact >= MULTI_BUCKET_IMPACT.LOW - ); + return point.isMultiBucketAnomaly === true; +} + +export function getMultiBucketImpactTooltipValue(point) { + const numFilledSquares = + point.multiBucketImpact !== undefined + ? getAnomalyScoreExplanationImpactValue(point.multiBucketImpact) + : 0; + return new Array(5) + .fill('\u25A0 ', 0, numFilledSquares) // Unicode filled square + .fill('\u25A1 ', numFilledSquares) // Unicode hollow square + .join(''); } export function numTicks(axisWidth) { diff --git a/x-pack/plugins/ml/public/application/util/chart_utils.test.js b/x-pack/plugins/ml/public/application/util/chart_utils.test.js index 83c7b58afbefd..f7bc96e1c18d1 100644 --- a/x-pack/plugins/ml/public/application/util/chart_utils.test.js +++ b/x-pack/plugins/ml/public/application/util/chart_utils.test.js @@ -44,7 +44,6 @@ import { showMultiBucketAnomalyTooltip, } from './chart_utils'; -import { MULTI_BUCKET_IMPACT } from '../../../common/constants/multi_bucket_impact'; import { CHART_TYPE } from '../explorer/explorer_constants'; timefilter.setTime({ @@ -159,44 +158,24 @@ describe('ML - chart utils', () => { }); describe('showMultiBucketAnomalyMarker', () => { - test('returns true for points with multiBucketImpact at or above medium impact', () => { - expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.HIGH })).toBe( - true - ); - expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.MEDIUM })).toBe( - true - ); + test('returns true for points with isMultiBucketAnomaly=true', () => { + expect(showMultiBucketAnomalyMarker({ isMultiBucketAnomaly: true })).toBe(true); }); test('returns false for points with multiBucketImpact missing or below medium impact', () => { expect(showMultiBucketAnomalyMarker({})).toBe(false); - expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.LOW })).toBe( - false - ); - expect(showMultiBucketAnomalyMarker({ multiBucketImpact: MULTI_BUCKET_IMPACT.NONE })).toBe( - false - ); + expect(showMultiBucketAnomalyMarker({ isMultiBucketAnomaly: false })).toBe(false); }); }); describe('showMultiBucketAnomalyTooltip', () => { - test('returns true for points with multiBucketImpact at or above low impact', () => { - expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.HIGH })).toBe( - true - ); - expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.MEDIUM })).toBe( - true - ); - expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.LOW })).toBe( - true - ); + test('returns true for points with isMultiBucketAnomaly=true', () => { + expect(showMultiBucketAnomalyTooltip({ isMultiBucketAnomaly: true })).toBe(true); }); test('returns false for points with multiBucketImpact missing or below medium impact', () => { expect(showMultiBucketAnomalyTooltip({})).toBe(false); - expect(showMultiBucketAnomalyTooltip({ multiBucketImpact: MULTI_BUCKET_IMPACT.NONE })).toBe( - false - ); + expect(showMultiBucketAnomalyTooltip({ isMultiBucketAnomaly: false })).toBe(false); }); }); diff --git a/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts b/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts index eb85ec82763e6..dbbf95d4806c2 100644 --- a/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts +++ b/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts @@ -37,6 +37,7 @@ import { aggregationTypeTransform, EntityField, getEntityFieldList, + isMultiBucketAnomaly, } from '../../../common/util/anomaly_utils'; import { InfluencersFilterQuery } from '../../../common/types/es_client'; import { isDefined } from '../../../common/types/guards'; @@ -1101,9 +1102,14 @@ export function anomalyChartsDataProvider(mlClient: MlClient, client: IScopedClu } } - if (record.multi_bucket_impact !== undefined) { - chartPoint.multiBucketImpact = record.multi_bucket_impact; + if ( + record.anomaly_score_explanation !== undefined && + record.anomaly_score_explanation.multi_bucket_impact !== undefined + ) { + chartPoint.multiBucketImpact = record.anomaly_score_explanation.multi_bucket_impact; } + + chartPoint.isMultiBucketAnomaly = isMultiBucketAnomaly(record); } }); diff --git a/x-pack/plugins/ml/server/routes/system.ts b/x-pack/plugins/ml/server/routes/system.ts index c067b8e3ff438..6ddc325154ed2 100644 --- a/x-pack/plugins/ml/server/routes/system.ts +++ b/x-pack/plugins/ml/server/routes/system.ts @@ -231,6 +231,7 @@ export function systemRoutes( indices.map(async (index) => client.asCurrentUser.indices.exists({ index, + allow_no_indices: false, }) ) ); diff --git a/x-pack/plugins/observability/common/data/sli_list.ts b/x-pack/plugins/observability/common/data/sli_list.ts new file mode 100644 index 0000000000000..cc630700fcd79 --- /dev/null +++ b/x-pack/plugins/observability/common/data/sli_list.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SLOList } from '../../public'; + +export const emptySloList: SLOList = { + results: [], + page: 1, + perPage: 25, + total: 0, +}; + +export const sloList: SLOList = { + results: [ + { + id: '1f1c6ee7-433f-4b56-b727-5682262e0d7d', + name: 'latency', + objective: { target: 0.98 }, + summary: { + sliValue: 0.99872, + errorBudget: { + remaining: 0.936, + }, + }, + }, + { + id: 'c0f8d669-9177-4706-9098-f397a88173a6', + name: 'availability', + objective: { target: 0.98 }, + summary: { + sliValue: 0.97, + errorBudget: { + remaining: 0, + }, + }, + }, + ], + page: 1, + perPage: 25, + total: 2, +}; diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index 5ed8c1934a4fa..14da44c470399 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -54,6 +54,7 @@ export const casesPath = '/cases'; export const uptimeOverviewLocatorID = 'UPTIME_OVERVIEW_LOCATOR'; export const syntheticsMonitorDetailLocatorID = 'SYNTHETICS_MONITOR_DETAIL_LOCATOR'; export const syntheticsEditMonitorLocatorID = 'SYNTHETICS_EDIT_MONITOR_LOCATOR'; +export const ruleDetailsLocatorID = 'RULE_DETAILS_LOCATOR'; export { NETWORK_TIMINGS_FIELDS, diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json index 43e69c1b4d952..fd131fb10add7 100644 --- a/x-pack/plugins/observability/kibana.json +++ b/x-pack/plugins/observability/kibana.json @@ -31,7 +31,8 @@ "inspector", "unifiedSearch", "security", - "guidedOnboarding" + "guidedOnboarding", + "share" ], "ui": true, "server": true, diff --git a/x-pack/plugins/observability/public/application/types.ts b/x-pack/plugins/observability/public/application/types.ts index e0d12548a2822..1bf29be34430d 100644 --- a/x-pack/plugins/observability/public/application/types.ts +++ b/x-pack/plugins/observability/public/application/types.ts @@ -22,6 +22,7 @@ import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { LensPublicStart } from '@kbn/lens-plugin/public'; +import { SharePluginStart } from '@kbn/share-plugin/public'; import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; import { CasesUiStart } from '@kbn/cases-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; @@ -39,6 +40,7 @@ export interface ObservabilityAppServices { notifications: NotificationsStart; overlays: OverlayStart; savedObjectsClient: SavedObjectsStart['client']; + share: SharePluginStart; stateTransfer: EmbeddableStateTransfer; storage: IStorageWrapper; theme: ThemeServiceStart; 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 index bdf565a29cbdb..d12bee2f87e47 100644 --- 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 @@ -5,57 +5,51 @@ * 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 { waitFor } from '@testing-library/react'; +import { timefilterServiceMock } from '@kbn/data-plugin/public/query/timefilter/timefilter_service.mock'; +import { useServices } from './services'; +import { ObservabilityAlertSearchBarProps } 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 useServicesMock = useServices 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( -

- ), - }, - }, + +jest.mock('./services'); + +const mockServices = () => { + useServicesMock.mockReturnValue({ + timeFilterService: timefilterServiceMock, + AlertsSearchBar: getAlertsSearchBarMock.mockReturnValue( +
+ ), + useToasts: jest.fn(), }); }; describe('ObservabilityAlertSearchBar', () => { - const renderComponent = (props: Partial = {}) => { - const alertSearchBarProps: AlertSearchBarProps = { + const renderComponent = (props: Partial = {}) => { + const observabilityAlertSearchBarProps: ObservabilityAlertSearchBarProps = { 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(), + onRangeFromChange: jest.fn(), + onRangeToChange: jest.fn(), + onKueryChange: jest.fn(), + onStatusChange: jest.fn(), + onEsQueryChange: jest.fn(), + rangeTo: 'now', + rangeFrom: 'now-15m', + status: 'all', ...props, }; - return render(); + return render(); }; beforeAll(() => { - mockKibana(); + mockServices(); }); beforeEach(() => { @@ -71,9 +65,7 @@ describe('ObservabilityAlertSearchBar', () => { }); it('should call alert search bar with correct props', () => { - act(() => { - renderComponent(); - }); + renderComponent(); expect(getAlertsSearchBarMock).toHaveBeenCalledWith( expect.objectContaining({ @@ -88,27 +80,71 @@ describe('ObservabilityAlertSearchBar', () => { }); it('should filter active alerts', async () => { - const mockedSetEsQuery = jest.fn(); + const mockedOnEsQueryChange = jest.fn(); const mockedFrom = '2022-11-15T09:38:13.604Z'; const mockedTo = '2022-11-15T09:53:13.604Z'; - const { getByTestId } = renderComponent({ - setEsQuery: mockedSetEsQuery, + + renderComponent({ + onEsQueryChange: mockedOnEsQueryChange, rangeFrom: mockedFrom, rangeTo: mockedTo, + status: 'active', }); - await act(async () => { - const activeButton = getByTestId(ACTIVE_BUTTON_DATA_TEST_SUBJ); - activeButton.click(); + expect(mockedOnEsQueryChange).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: [], + }, }); + }); + + it('should include defaultSearchQueries in es query', async () => { + const mockedOnEsQueryChange = jest.fn(); + const mockedFrom = '2022-11-15T09:38:13.604Z'; + const mockedTo = '2022-11-15T09:53:13.604Z'; + const defaultSearchQueries = [ + { + query: 'kibana.alert.rule.uuid: 413a9631-1a29-4344-a8b4-9a1dc23421ee', + language: 'kuery', + }, + ]; - expect(mockedSetEsQuery).toHaveBeenCalledWith({ + renderComponent({ + onEsQueryChange: mockedOnEsQueryChange, + rangeFrom: mockedFrom, + rangeTo: mockedTo, + defaultSearchQueries, + status: 'all', + }); + + expect(mockedOnEsQueryChange).toHaveBeenCalledWith({ bool: { filter: [ { bool: { minimum_should_match: 1, - should: [{ match_phrase: { 'kibana.alert.status': 'active' } }], + should: [ + { match: { 'kibana.alert.rule.uuid': '413a9631-1a29-4344-a8b4-9a1dc23421ee' } }, + ], }, }, { 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 2bcedac677fa9..3d8de61482694 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 @@ -6,16 +6,15 @@ */ 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 '../../../utils/kibana_react'; -import { observabilityAlertFeatureIds } from '../../../config'; -import { ObservabilityAppServices } from '../../../application/types'; +import { useServices } from './services'; import { AlertsStatusFilter } from './components'; +import { observabilityAlertFeatureIds } from '../../../config'; import { ALERT_STATUS_QUERY, DEFAULT_QUERIES, DEFAULT_QUERY_STRING } from './constants'; -import { AlertSearchBarProps } from './types'; +import { ObservabilityAlertSearchBarProps } from './types'; import { buildEsQuery } from '../../../utils/build_es_query'; import { AlertStatus } from '../../../../common/typings'; @@ -27,83 +26,79 @@ const getAlertStatusQuery = (status: string): Query[] => { export function ObservabilityAlertSearchBar({ appName, + defaultSearchQueries = DEFAULT_QUERIES, + onEsQueryChange, + onKueryChange, + onRangeFromChange, + onRangeToChange, + onStatusChange, + kuery, rangeFrom, - setRangeFrom, rangeTo, - setRangeTo, - kuery, - setKuery, status, - setStatus, - setEsQuery, - queries = DEFAULT_QUERIES, -}: AlertSearchBarProps) { - const { - data: { - query: { - timefilter: { timefilter: timeFilterService }, - }, - }, - notifications: { toasts }, - triggersActionsUi: { getAlertsSearchBar: AlertsSearchBar }, - } = useKibana().services; +}: ObservabilityAlertSearchBarProps) { + const { AlertsSearchBar, timeFilterService, useToasts } = useServices(); + const toasts = useToasts(); - const onStatusChange = useCallback( + const onAlertStatusChange = useCallback( (alertStatus: AlertStatus) => { - setEsQuery( + onEsQueryChange( buildEsQuery( { to: rangeTo, from: rangeFrom, }, kuery, - [...getAlertStatusQuery(alertStatus), ...queries] + [...getAlertStatusQuery(alertStatus), ...defaultSearchQueries] ) ); }, - [kuery, queries, rangeFrom, rangeTo, setEsQuery] + [kuery, defaultSearchQueries, rangeFrom, rangeTo, onEsQueryChange] ); useEffect(() => { - onStatusChange(status); - }, [onStatusChange, status]); + onAlertStatusChange(status); + }, [onAlertStatusChange, status]); - const onSearchBarParamsChange = useCallback( + const onSearchBarParamsChange = useCallback< + (query: { + dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' }; + query: string; + }) => void + >( ({ dateRange, query }) => { try { // First try to create es query to make sure query is valid, then save it in state const esQuery = buildEsQuery( { - to: rangeTo, - from: rangeFrom, + to: dateRange.to, + from: dateRange.from, }, query, - [...getAlertStatusQuery(status), ...queries] + [...getAlertStatusQuery(status), ...defaultSearchQueries] ); - setKuery(query); + onKueryChange(query); timeFilterService.setTime(dateRange); - setRangeFrom(dateRange.from); - setRangeTo(dateRange.to); - setEsQuery(esQuery); + onRangeFromChange(dateRange.from); + onRangeToChange(dateRange.to); + onEsQueryChange(esQuery); } catch (error) { toasts.addError(error, { title: i18n.translate('xpack.observability.alerts.searchBar.invalidQueryTitle', { defaultMessage: 'Invalid query string', }), }); - setKuery(DEFAULT_QUERY_STRING); + onKueryChange(DEFAULT_QUERY_STRING); } }, [ + defaultSearchQueries, timeFilterService, - setRangeFrom, - setRangeTo, - setKuery, - setEsQuery, - rangeTo, - rangeFrom, + onRangeFromChange, + onRangeToChange, + onKueryChange, + onEsQueryChange, status, - queries, toasts, ] ); @@ -124,15 +119,13 @@ export function ObservabilityAlertSearchBar({ - { - setStatus(id as AlertStatus); - }} - /> + ); } + +// eslint-disable-next-line import/no-default-export +export default ObservabilityAlertSearchBar; diff --git a/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar_with_url_sync.tsx b/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar_with_url_sync.tsx index 203d207ce9a10..405382f49b47f 100644 --- a/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar_with_url_sync.tsx +++ b/x-pack/plugins/observability/public/components/shared/alert_search_bar/alert_search_bar_with_url_sync.tsx @@ -12,13 +12,26 @@ import { useAlertSearchBarStateContainer, } from './containers'; import { ObservabilityAlertSearchBar } from './alert_search_bar'; +import { ObservabilityAlertSearchBarProvider } from './services'; import { AlertSearchBarWithUrlSyncProps } from './types'; +import { useKibana } from '../../../utils/kibana_react'; +import { ObservabilityAppServices } from '../../../application/types'; +import { useToasts } from '../../../hooks/use_toast'; function AlertSearchbarWithUrlSync(props: AlertSearchBarWithUrlSyncProps) { const { urlStorageKey, ...searchBarProps } = props; const stateProps = useAlertSearchBarStateContainer(urlStorageKey); + const { data, triggersActionsUi } = useKibana().services; - return ; + return ( + + + + ); } export function ObservabilityAlertSearchbarWithUrlSync(props: AlertSearchBarWithUrlSyncProps) { diff --git a/x-pack/plugins/observability/public/components/shared/alert_search_bar/components/alerts_status_filter.tsx b/x-pack/plugins/observability/public/components/shared/alert_search_bar/components/alerts_status_filter.tsx index fa1f362120713..5dbbfd06d744a 100644 --- a/x-pack/plugins/observability/public/components/shared/alert_search_bar/components/alerts_status_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/alert_search_bar/components/alerts_status_filter.tsx @@ -6,9 +6,11 @@ */ import { EuiButtonGroup, EuiButtonGroupOptionProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; import { ALL_ALERTS, ACTIVE_ALERTS, RECOVERED_ALERTS } from '../constants'; import { AlertStatusFilterProps } from '../types'; +import { AlertStatus } from '../../../../../common/typings'; const options: EuiButtonGroupOptionProps[] = [ { @@ -34,11 +36,13 @@ const options: EuiButtonGroupOptionProps[] = [ export function AlertsStatusFilter({ status, onChange }: AlertStatusFilterProps) { return ( onChange(id as AlertStatus)} /> ); } diff --git a/x-pack/plugins/observability/public/components/shared/alert_search_bar/containers/use_alert_search_bar_state_container.tsx b/x-pack/plugins/observability/public/components/shared/alert_search_bar/containers/use_alert_search_bar_state_container.tsx index f403d8643a945..cae32f3a9e304 100644 --- a/x-pack/plugins/observability/public/components/shared/alert_search_bar/containers/use_alert_search_bar_state_container.tsx +++ b/x-pack/plugins/observability/public/components/shared/alert_search_bar/containers/use_alert_search_bar_state_container.tsx @@ -52,14 +52,14 @@ export function useAlertSearchBarStateContainer(urlStorageKey: string) { ); return { + kuery, + onKueryChange: setKuery, + onRangeFromChange: setRangeFrom, + onRangeToChange: setRangeTo, + onStatusChange: setStatus, rangeFrom, - setRangeFrom, rangeTo, - setRangeTo, - kuery, - setKuery, status, - setStatus, }; } diff --git a/x-pack/plugins/observability/public/components/shared/alert_search_bar/services.tsx b/x-pack/plugins/observability/public/components/shared/alert_search_bar/services.tsx new file mode 100644 index 0000000000000..186178d6747d2 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/alert_search_bar/services.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC, useContext } from 'react'; +import { ObservabilityAlertSearchBarDependencies, Services } from './types'; + +const ObservabilityAlertSearchBarContext = React.createContext(null); + +export const ObservabilityAlertSearchBarProvider: FC = ({ + children, + data: { + query: { + timefilter: { timefilter: timeFilterService }, + }, + }, + useToasts, + triggersActionsUi: { getAlertsSearchBar: AlertsSearchBar }, +}) => { + const services = { + timeFilterService, + useToasts, + AlertsSearchBar, + }; + return ( + + {children} + + ); +}; + +export function useServices() { + const context = useContext(ObservabilityAlertSearchBarContext); + + if (!context) { + throw new Error( + 'ObservabilityAlertSearchBarContext is missing. Ensure your component or React root is wrapped with ObservabilityAlertSearchBarProvider.' + ); + } + + return context; +} 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 b9e3c706b18a0..be3ccce505505 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 @@ -5,14 +5,46 @@ * 2.0. */ +import { ReactElement } from 'react'; +import { ToastsStart } from '@kbn/core-notifications-browser'; +import { TimefilterContract } from '@kbn/data-plugin/public'; +import { AlertsSearchBarProps } from '@kbn/triggers-actions-ui-plugin/public/application/sections/alerts_search_bar'; import { BoolQuery, Query } from '@kbn/es-query'; import { AlertStatus } from '../../../../common/typings'; export interface AlertStatusFilterProps { status: AlertStatus; - onChange: (id: string, value: string) => void; + onChange: (id: AlertStatus) => void; } +export interface AlertSearchBarWithUrlSyncProps extends CommonAlertSearchBarProps { + urlStorageKey: string; +} + +export interface Dependencies { + data: { + query: { + timefilter: { timefilter: TimefilterContract }; + }; + }; + triggersActionsUi: { + getAlertsSearchBar: (props: AlertsSearchBarProps) => ReactElement; + }; + useToasts: () => ToastsStart; +} + +export type ObservabilityAlertSearchBarDependencies = Dependencies; + +export interface Services { + timeFilterService: TimefilterContract; + AlertsSearchBar: (props: AlertsSearchBarProps) => ReactElement; + useToasts: () => ToastsStart; +} + +export type ObservabilityAlertSearchBarProps = AlertSearchBarContainerState & + AlertSearchBarStateTransitions & + CommonAlertSearchBarProps; + interface AlertSearchBarContainerState { rangeFrom: string; rangeTo: string; @@ -21,23 +53,14 @@ interface AlertSearchBarContainerState { } interface AlertSearchBarStateTransitions { - setRangeFrom: (rangeFrom: string) => void; - setRangeTo: (rangeTo: string) => void; - setKuery: (kuery: string) => void; - setStatus: (status: AlertStatus) => void; + onRangeFromChange: (rangeFrom: string) => void; + onRangeToChange: (rangeTo: string) => void; + onKueryChange: (kuery: string) => void; + onStatusChange: (status: AlertStatus) => void; } -export interface CommonAlertSearchBarProps { +interface CommonAlertSearchBarProps { appName: string; - setEsQuery: (query: { bool: BoolQuery }) => void; - queries?: Query[]; + onEsQueryChange: (query: { bool: BoolQuery }) => void; + defaultSearchQueries?: Query[]; } - -export interface AlertSearchBarWithUrlSyncProps extends CommonAlertSearchBarProps { - urlStorageKey: string; -} - -export interface AlertSearchBarProps - extends AlertSearchBarContainerState, - AlertSearchBarStateTransitions, - CommonAlertSearchBarProps {} diff --git a/x-pack/plugins/observability/public/components/shared/index.tsx b/x-pack/plugins/observability/public/components/shared/index.tsx index fab875ebaf078..5c3dbc083e285 100644 --- a/x-pack/plugins/observability/public/components/shared/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/index.tsx @@ -8,6 +8,7 @@ import React, { lazy, Suspense } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; import { LoadWhenInViewProps } from './load_when_in_view/load_when_in_view'; +import { ObservabilityAlertSearchBarProps } from './alert_search_bar/types'; import type { CoreVitalProps, HeaderMenuPortalProps } from './types'; import type { FieldValueSuggestionsProps, @@ -113,3 +114,13 @@ export function LoadWhenInView(props: LoadWhenInViewProps) { ); } + +const ObservabilityAlertSearchBarLazy = lazy(() => import('./alert_search_bar/alert_search_bar')); + +export function ObservabilityAlertSearchBar(props: ObservabilityAlertSearchBarProps) { + return ( + }> + + + ); +} diff --git a/x-pack/plugins/observability/public/components/shared/slo/slo_selector/slo_selector.stories.tsx b/x-pack/plugins/observability/public/components/shared/slo/slo_selector/slo_selector.stories.tsx new file mode 100644 index 0000000000000..0d1c6372c0513 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/slo/slo_selector/slo_selector.stories.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ComponentStory } from '@storybook/react'; + +import { SLO } from '../../../../typings'; +import { SloSelector as Component } from './slo_selector'; + +export default { + component: Component, + title: 'app/SLOs/Shared/SloSelector', +}; + +const Template: ComponentStory = () => ( + console.log(slo)} /> +); +const defaultProps = {}; + +export const Default = Template.bind({}); +Default.args = defaultProps; diff --git a/x-pack/plugins/observability/public/components/shared/slo/slo_selector/slo_selector.test.tsx b/x-pack/plugins/observability/public/components/shared/slo/slo_selector/slo_selector.test.tsx new file mode 100644 index 0000000000000..77da476c1ff16 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/slo/slo_selector/slo_selector.test.tsx @@ -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 React from 'react'; +import { act, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { render } from '../../../../utils/test_helper'; +import { SloSelector } from './slo_selector'; +import { useFetchSloList } from '../../../../hooks/slo/use_fetch_slo_list'; +import { emptySloList } from '../../../../pages/slos/mocks/slo_list'; +import { wait } from '@testing-library/user-event/dist/utils'; + +jest.mock('../../../../hooks/slo/use_fetch_slo_list'); +const useFetchSloListMock = useFetchSloList as jest.Mock; + +describe('SLO Selector', () => { + const onSelectedSpy = jest.fn(); + beforeEach(() => { + jest.clearAllMocks(); + useFetchSloListMock.mockReturnValue({ loading: true, sloList: emptySloList }); + }); + + it('fetches SLOs asynchronously', async () => { + render(); + + expect(screen.getByTestId('sloSelector')).toBeTruthy(); + expect(useFetchSloListMock).toHaveBeenCalledWith(''); + }); + + it('searches SLOs when typing', async () => { + render(); + + const input = screen.getByTestId('comboBoxInput'); + await act(async () => { + await userEvent.type(input, 'latency', { delay: 1 }); + await wait(310); // debounce delay + }); + + expect(useFetchSloListMock).toHaveBeenCalledWith('latency'); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/slo/slo_selector/slo_selector.tsx b/x-pack/plugins/observability/public/components/shared/slo/slo_selector/slo_selector.tsx new file mode 100644 index 0000000000000..14e36da90fbbd --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/slo/slo_selector/slo_selector.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 { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { debounce } from 'lodash'; +import React, { useEffect, useMemo, useState } from 'react'; + +import { SLO } from '../../../../typings'; +import { useFetchSloList } from '../../../../hooks/slo/use_fetch_slo_list'; + +interface Props { + onSelected: (slo: SLO) => void; +} + +function SloSelector({ onSelected }: Props) { + const [options, setOptions] = useState>>([]); + const [selectedOptions, setSelected] = useState>>(); + const [searchValue, setSearchValue] = useState(''); + const { loading, sloList } = useFetchSloList(searchValue); + + useEffect(() => { + const isLoadedWithData = !loading && sloList !== undefined; + const opts: Array> = isLoadedWithData + ? sloList.results.map((slo) => ({ value: slo.id, label: slo.name })) + : []; + setOptions(opts); + }, [loading, sloList]); + + const onChange = (opts: Array>) => { + setSelected(opts); + if (opts.length === 1) { + const sloId = opts[0].value; + const selectedSlo = sloList.results.find((slo) => slo.id === sloId); + if (selectedSlo !== undefined) { + onSelected(selectedSlo); + } + } + }; + + const onSearchChange = useMemo(() => debounce((value: string) => setSearchValue(value), 300), []); + + return ( + + ); +} + +export { SloSelector }; +export type { Props as SloSelectorProps }; diff --git a/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts new file mode 100644 index 0000000000000..47f39f55cf895 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/slo/__storybook_mocks__/use_fetch_slo_list.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { sloList } from '../../../../common/data/sli_list'; +import { UseFetchSloListResponse } from '../use_fetch_slo_list'; + +export const useFetchSloList = (name?: string): UseFetchSloListResponse => { + return { + loading: false, + sloList, + }; +}; diff --git a/x-pack/plugins/observability/public/pages/slos/hooks/use_fetch_slo_list.ts b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts similarity index 67% rename from x-pack/plugins/observability/public/pages/slos/hooks/use_fetch_slo_list.ts rename to x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts index 059635cb3dbbb..2a9c41d6d3dc4 100644 --- a/x-pack/plugins/observability/public/pages/slos/hooks/use_fetch_slo_list.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_fetch_slo_list.ts @@ -8,8 +8,8 @@ import { useCallback, useMemo } from 'react'; import { HttpSetup } from '@kbn/core/public'; -import type { SLO, SLOList } from '../../../typings/slo'; -import { useDataFetcher } from '../../../hooks/use_data_fetcher'; +import type { SLO, SLOList } from '../../typings/slo'; +import { useDataFetcher } from '../use_data_fetcher'; const EMPTY_LIST = { results: [], @@ -18,28 +18,42 @@ const EMPTY_LIST = { perPage: 0, }; -export const useFetchSloList = (): [boolean, SLOList] => { - const params = useMemo(() => ({}), []); - const shouldExecuteApiCall = useCallback((apiCallParams: {}) => true, []); +interface SLOListParams { + name?: string; +} + +interface UseFetchSloListResponse { + loading: boolean; + sloList: SLOList; +} - const { loading, data: sloList } = useDataFetcher<{}, SLOList>({ +const useFetchSloList = (name?: string): UseFetchSloListResponse => { + const params: SLOListParams = useMemo(() => ({ name }), [name]); + const shouldExecuteApiCall = useCallback( + (apiCallParams: SLOListParams) => apiCallParams.name === params.name, + [params] + ); + + const { loading, data: sloList } = useDataFetcher({ paramsForApiCall: params, initialDataState: EMPTY_LIST, executeApiCall: fetchSloList, shouldExecuteApiCall, }); - return [loading, sloList]; + return { loading, sloList }; }; const fetchSloList = async ( - params: {}, + params: SLOListParams, abortController: AbortController, http: HttpSetup ): Promise => { try { const response = await http.get>(`/api/observability/slos`, { - query: {}, + query: { + ...(params.name && { name: params.name }), + }, signal: abortController.signal, }); if (response !== undefined) { @@ -78,3 +92,6 @@ function toSLO(result: any): SLO { }, }; } + +export { useFetchSloList }; +export type { UseFetchSloListResponse }; diff --git a/x-pack/plugins/observability/public/hooks/use_toast.ts b/x-pack/plugins/observability/public/hooks/use_toast.ts new file mode 100644 index 0000000000000..380d57bb28b63 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_toast.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ObservabilityAppServices } from '../application/types'; + +export const useToasts = () => useKibana().services.notifications.toasts; diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 37ee74157ede3..2f2b273433875 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -47,6 +47,8 @@ export * from './components/shared/action_menu'; export type { UXMetrics } from './components/shared/core_web_vitals'; export { DatePickerContextProvider } from './context/date_picker_context'; +export { ObservabilityAlertSearchBarProvider } from './components/shared/alert_search_bar/services'; + export { getCoreVitalsComponent, HeaderMenuPortal, @@ -57,6 +59,7 @@ export { ExploratoryView, DatePicker, LoadWhenInView, + ObservabilityAlertSearchBar, } from './components/shared'; export type { LazyObservabilityPageTemplateProps } from './components/shared'; diff --git a/x-pack/plugins/observability/public/locators/rule_details.test.ts b/x-pack/plugins/observability/public/locators/rule_details.test.ts new file mode 100644 index 0000000000000..f63354d29d90a --- /dev/null +++ b/x-pack/plugins/observability/public/locators/rule_details.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ACTIVE_ALERTS } from '../components/shared/alert_search_bar/constants'; +import { EXECUTION_TAB, ALERTS_TAB } from '../pages/rule_details/constants'; +import { getRuleDetailsPath, RuleDetailsLocatorDefinition } from './rule_details'; + +describe('RuleDetailsLocator', () => { + const locator = new RuleDetailsLocatorDefinition(); + const mockedRuleId = '389d3318-7e10-4996-bb45-128e1607fb7e'; + + it('should return correct url when only ruleId is provided', async () => { + const location = await locator.getLocation({ ruleId: mockedRuleId }); + expect(location.app).toEqual('observability'); + expect(location.path).toEqual(getRuleDetailsPath(mockedRuleId)); + }); + + it('should return correct url when tabId is execution', async () => { + const location = await locator.getLocation({ ruleId: mockedRuleId, tabId: EXECUTION_TAB }); + expect(location.path).toMatchInlineSnapshot( + `"/alerts/rules/389d3318-7e10-4996-bb45-128e1607fb7e?tabId=execution"` + ); + }); + + it('should return correct url when tabId is alerts without extra search params', async () => { + const location = await locator.getLocation({ ruleId: mockedRuleId, tabId: ALERTS_TAB }); + expect(location.path).toMatchInlineSnapshot( + `"/alerts/rules/389d3318-7e10-4996-bb45-128e1607fb7e?tabId=alerts&searchBarParams=(kuery:'',rangeFrom:now-15m,rangeTo:now,status:all)"` + ); + }); + + it('should return correct url when tabId is alerts with search params', async () => { + const location = await locator.getLocation({ + ruleId: mockedRuleId, + tabId: ALERTS_TAB, + rangeFrom: 'mockedRangeTo', + rangeTo: 'mockedRangeFrom', + kuery: 'mockedKuery', + status: ACTIVE_ALERTS.status, + }); + expect(location.path).toMatchInlineSnapshot( + `"/alerts/rules/389d3318-7e10-4996-bb45-128e1607fb7e?tabId=alerts&searchBarParams=(kuery:mockedKuery,rangeFrom:mockedRangeTo,rangeTo:mockedRangeFrom,status:active)"` + ); + }); +}); diff --git a/x-pack/plugins/observability/public/locators/rule_details.ts b/x-pack/plugins/observability/public/locators/rule_details.ts new file mode 100644 index 0000000000000..13eaddb5b4707 --- /dev/null +++ b/x-pack/plugins/observability/public/locators/rule_details.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; +import type { SerializableRecord } from '@kbn/utility-types'; +import type { LocatorDefinition } from '@kbn/share-plugin/public'; +import { ruleDetailsLocatorID } from '../../common'; +import { ALL_ALERTS } from '../components/shared/alert_search_bar/constants'; +import { + ALERTS_TAB, + EXECUTION_TAB, + SEARCH_BAR_URL_STORAGE_KEY, +} from '../pages/rule_details/constants'; +import type { TabId } from '../pages/rule_details/types'; +import type { AlertStatus } from '../../common/typings'; + +export interface RuleDetailsLocatorParams extends SerializableRecord { + ruleId: string; + tabId?: TabId; + rangeFrom?: string; + rangeTo?: string; + kuery?: string; + status?: AlertStatus; +} + +export const getRuleDetailsPath = (ruleId: string) => { + return `/alerts/rules/${encodeURI(ruleId)}`; +}; + +export class RuleDetailsLocatorDefinition implements LocatorDefinition { + public readonly id = ruleDetailsLocatorID; + + public readonly getLocation = async (params: RuleDetailsLocatorParams) => { + const { ruleId, kuery, rangeTo, tabId, rangeFrom, status } = params; + const appState: { + tabId?: TabId; + rangeFrom?: string; + rangeTo?: string; + kuery?: string; + status?: AlertStatus; + } = {}; + + appState.rangeFrom = rangeFrom || 'now-15m'; + appState.rangeTo = rangeTo || 'now'; + appState.kuery = kuery || ''; + appState.status = status || ALL_ALERTS.status; + + let path = getRuleDetailsPath(ruleId); + + if (tabId === ALERTS_TAB) { + path = `${path}?tabId=${tabId}`; + path = setStateToKbnUrl( + SEARCH_BAR_URL_STORAGE_KEY, + appState, + { useHash: false, storeInHashQuery: false }, + path + ); + } else if (tabId === EXECUTION_TAB) { + path = `${path}?tabId=${tabId}`; + } + + return { + app: 'observability', + path, + state: {}, + }; + }; +} diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx index f1f4e14baf337..e2a6915fd79b6 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx @@ -136,7 +136,7 @@ export function AlertsPage() { diff --git a/x-pack/plugins/observability/public/pages/rule_details/constants.ts b/x-pack/plugins/observability/public/pages/rule_details/constants.ts index 8d23754f60ef1..ffca250a9f15f 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/constants.ts +++ b/x-pack/plugins/observability/public/pages/rule_details/constants.ts @@ -7,7 +7,7 @@ export const EXECUTION_TAB = 'execution'; export const ALERTS_TAB = 'alerts'; -export const URL_STORAGE_KEY = 'searchBarParams'; +export const SEARCH_BAR_URL_STORAGE_KEY = 'searchBarParams'; export const EVENT_ERROR_LOG_TAB = 'rule_error_log_list'; export const RULE_DETAILS_PAGE_ID = 'rule-details-alerts-o11y'; export const RULE_DETAILS_ALERTS_SEARCH_BAR_ID = 'rule-details-alerts-search-bar-o11y'; diff --git a/x-pack/plugins/observability/public/pages/rule_details/index.tsx b/x-pack/plugins/observability/public/pages/rule_details/index.tsx index 12732939ad66c..1e0e95868f888 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/index.tsx +++ b/x-pack/plugins/observability/public/pages/rule_details/index.tsx @@ -46,7 +46,7 @@ import { ALERTS_TAB, RULE_DETAILS_PAGE_ID, RULE_DETAILS_ALERTS_SEARCH_BAR_ID, - URL_STORAGE_KEY, + SEARCH_BAR_URL_STORAGE_KEY, } from './constants'; import { RuleDetailsPathParams, TabId } from './types'; import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; @@ -57,7 +57,9 @@ import { PageTitle } from './components'; import { getHealthColor } from './config'; import { hasExecuteActionsCapability, hasAllPrivilege } from './config'; import { paths } from '../../config/paths'; -import { observabilityFeatureId } from '../../../common'; +import { ALERT_STATUS_ALL } from '../../../common/constants'; +import { AlertStatus } from '../../../common/typings'; +import { observabilityFeatureId, ruleDetailsLocatorID } from '../../../common'; import { ALERT_STATUS_LICENSE_ERROR, rulesStatusesTranslationsMapping } from './translations'; import { ObservabilityAppServices } from '../../application/types'; @@ -70,12 +72,15 @@ export function RuleDetailsPage() { getEditAlertFlyout, getRuleEventLogList, getAlertsStateTable: AlertsStateTable, - getRuleAlertsSummary, + getRuleAlertsSummary: AlertSummaryWidget, getRuleStatusPanel, getRuleDefinition, }, application: { capabilities, navigateToUrl }, notifications: { toasts }, + share: { + url: { locators }, + }, } = useKibana().services; const { ruleId } = useParams(); @@ -106,6 +111,22 @@ export function RuleDetailsPage() { const ruleQuery = useRef([ { query: `kibana.alert.rule.uuid: ${ruleId}`, language: 'kuery' }, ] as Query[]); + const tabsRef = useRef(null); + + const onAlertSummaryWidgetClick = async (status: AlertStatus = ALERT_STATUS_ALL) => { + await locators.get(ruleDetailsLocatorID)?.navigate( + { + ruleId, + tabId: ALERTS_TAB, + status, + }, + { + replace: true, + } + ); + setTabId(ALERTS_TAB); + tabsRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; const updateUrl = (nextQuery: { tabId: TabId }) => { const newTabId = nextQuery.tabId; @@ -223,9 +244,9 @@ export function RuleDetailsPage() { @@ -354,16 +375,18 @@ export function RuleDetailsPage() { - {getRuleAlertsSummary({ - rule, - filteredRuleTypes, - })} + onAlertSummaryWidgetClick(status)} + /> {getRuleDefinition({ rule, onEditRule: () => reloadRule() } as RuleDefinitionProps)} +
; + } + + return <>{slo && slo.name}; +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.stories.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.stories.tsx new file mode 100644 index 0000000000000..11d05c95f7512 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.stories.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 { ComponentStory } from '@storybook/react'; + +import { SloDetails as Component, Props } from './slo_details'; +import { anSLO } from '../mocks/slo'; + +export default { + component: Component, + title: 'app/SLO/DetailsPage/SloDetails', + argTypes: {}, +}; + +const Template: ComponentStory = (props: Props) => ; + +const defaultProps: Props = { + slo: anSLO, +}; + +export const SloDetails = Template.bind({}); +SloDetails.args = defaultProps; diff --git a/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx new file mode 100644 index 0000000000000..62e2d1d1be0d0 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/components/slo_details.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { SLO } from '../../../typings'; + +export interface Props { + slo: SLO; +} + +export function SloDetails(props: Props) { + const { slo } = props; + return
{JSON.stringify(slo, null, 2)}
; +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/hooks/use_fetch_slo_details.ts b/x-pack/plugins/observability/public/pages/slo_details/hooks/use_fetch_slo_details.ts new file mode 100644 index 0000000000000..6044f8a9ba8d4 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/hooks/use_fetch_slo_details.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpSetup } from '@kbn/core-http-browser'; +import { useCallback, useMemo } from 'react'; +import { useDataFetcher } from '../../../hooks/use_data_fetcher'; +import { SLO } from '../../../typings'; + +interface UseFetchSloDetailsResponse { + loading: boolean; + slo: SLO | undefined; +} + +function useFetchSloDetails(sloId: string): UseFetchSloDetailsResponse { + const params = useMemo(() => ({ sloId }), [sloId]); + const shouldExecuteApiCall = useCallback( + (apiCallParams: { sloId: string }) => params.sloId === apiCallParams.sloId, + [params] + ); + + const { loading, data: slo } = useDataFetcher<{ sloId: string }, SLO | undefined>({ + paramsForApiCall: params, + initialDataState: undefined, + executeApiCall: fetchSlo, + shouldExecuteApiCall, + }); + + return { loading, slo }; +} + +const fetchSlo = async ( + params: { sloId: string }, + abortController: AbortController, + http: HttpSetup +): Promise => { + try { + const response = await http.get>( + `/api/observability/slos/${params.sloId}`, + { + query: {}, + signal: abortController.signal, + } + ); + if (response !== undefined) { + return toSLO(response); + } + } catch (error) { + // ignore error for retrieving slos + } + + return undefined; +}; + +function toSLO(result: any): SLO { + return { + id: String(result.id), + name: String(result.name), + objective: { target: Number(result.objective.target) }, + summary: { + sliValue: Number(result.summary.sli_value), + errorBudget: { + remaining: Number(result.summary.error_budget.remaining), + }, + }, + }; +} + +export type { UseFetchSloDetailsResponse }; +export { useFetchSloDetails }; diff --git a/x-pack/plugins/observability/public/pages/slo_details/index.test.tsx b/x-pack/plugins/observability/public/pages/slo_details/index.test.tsx new file mode 100644 index 0000000000000..fa40471708eec --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/index.test.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { screen } from '@testing-library/react'; + +import { ConfigSchema } from '../../plugin'; +import { Subset } from '../../typings'; +import { useKibana } from '../../utils/kibana_react'; +import { kibanaStartMock } from '../../utils/kibana_react.mock'; +import { render } from '../../utils/test_helper'; +import { SloDetailsPage } from '.'; +import { useFetchSloDetails } from './hooks/use_fetch_slo_details'; +import { anSLO } from './mocks/slo'; +import { useParams } from 'react-router-dom'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: jest.fn(), +})); + +jest.mock('./hooks/use_fetch_slo_details'); +jest.mock('../../utils/kibana_react'); +jest.mock('../../hooks/use_breadcrumbs'); + +const useFetchSloDetailsMock = useFetchSloDetails as jest.Mock; +const useParamsMock = useParams as jest.Mock; +const useKibanaMock = useKibana as jest.Mock; +const mockKibana = () => { + useKibanaMock.mockReturnValue({ + services: { + ...kibanaStartMock.startContract(), + http: { + basePath: { + prepend: jest.fn(), + }, + }, + }, + }); +}; + +const config: Subset = { + unsafe: { + slo: { enabled: true }, + }, +}; + +describe('SLO Details Page', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockKibana(); + }); + + it('renders the not found page when the feature flag is not enabled', async () => { + useParamsMock.mockReturnValue(anSLO.id); + useFetchSloDetailsMock.mockReturnValue({ loading: false, slo: anSLO }); + render(, { unsafe: { slo: { enabled: false } } }); + + expect(screen.queryByTestId('pageNotFound')).toBeTruthy(); + }); + + it('renders the not found page when the SLO cannot be found', async () => { + useParamsMock.mockReturnValue('inexistant'); + useFetchSloDetailsMock.mockReturnValue({ loading: false, slo: undefined }); + render(, config); + + expect(screen.queryByTestId('pageNotFound')).toBeTruthy(); + }); + + it('renders the loading spiner when fetching the SLO', async () => { + useParamsMock.mockReturnValue(anSLO.id); + useFetchSloDetailsMock.mockReturnValue({ loading: true, slo: undefined }); + render(, config); + + expect(screen.queryByTestId('pageNotFound')).toBeFalsy(); + expect(screen.queryByTestId('loadingTitle')).toBeTruthy(); + expect(screen.queryByTestId('loadingDetails')).toBeTruthy(); + }); + + it('renders the SLO details page when the feature flag is enabled', async () => { + useParamsMock.mockReturnValue(anSLO.id); + useFetchSloDetailsMock.mockReturnValue({ loading: false, slo: anSLO }); + render(, config); + + expect(screen.queryByTestId('sloDetailsPage')).toBeTruthy(); + expect(screen.queryByTestId('sloDetails')).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/slo_details/index.tsx b/x-pack/plugins/observability/public/pages/slo_details/index.tsx new file mode 100644 index 0000000000000..45a46e4d89490 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/index.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useParams } from 'react-router-dom'; +import { IBasePath } from '@kbn/core-http-browser'; +import { EuiBreadcrumbProps } from '@elastic/eui/src/components/breadcrumbs/breadcrumb'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { ObservabilityAppServices } from '../../application/types'; +import { paths } from '../../config'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; +import { useKibana } from '../../utils/kibana_react'; +import PageNotFound from '../404'; +import { isSloFeatureEnabled } from '../slos/helpers'; +import { SLOS_BREADCRUMB_TEXT } from '../slos/translations'; +import { SloDetailsPathParams } from './types'; +import { useFetchSloDetails } from './hooks/use_fetch_slo_details'; +import { SLO } from '../../typings'; +import { SloDetails } from './components/slo_details'; +import { SLO_DETAILS_BREADCRUMB_TEXT } from './translations'; +import { PageTitle } from './components/page_title'; + +export function SloDetailsPage() { + const { http } = useKibana().services; + const { ObservabilityPageTemplate, config } = usePluginContext(); + const { sloId } = useParams(); + + const { loading, slo } = useFetchSloDetails(sloId); + useBreadcrumbs(getBreadcrumbs(http.basePath, slo)); + + const isSloNotFound = !loading && slo === undefined; + if (!isSloFeatureEnabled(config) || isSloNotFound) { + return ; + } + + return ( + , + rightSideItems: [], + bottomBorder: true, + }} + data-test-subj="sloDetailsPage" + > + {loading && } + {!loading && } + + ); +} + +function getBreadcrumbs(basePath: IBasePath, slo: SLO | undefined): EuiBreadcrumbProps[] { + return [ + { + href: basePath.prepend(paths.observability.slos), + text: SLOS_BREADCRUMB_TEXT, + }, + { + text: slo?.name ?? SLO_DETAILS_BREADCRUMB_TEXT, + }, + ]; +} diff --git a/x-pack/plugins/observability/public/pages/slo_details/mocks/slo.ts b/x-pack/plugins/observability/public/pages/slo_details/mocks/slo.ts new file mode 100644 index 0000000000000..a657629bd5ecb --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/mocks/slo.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 { SLO } from '../../../typings'; + +export const anSLO: SLO = { + id: '2f17deb0-725a-11ed-ab7c-4bb641cfc57e', + name: 'SLO latency service log', + objective: { + target: 0.98, + }, + summary: { + sliValue: 0.990097, + errorBudget: { + remaining: 0.504831, + }, + }, +}; diff --git a/x-pack/plugins/observability/public/pages/slo_details/translations.ts b/x-pack/plugins/observability/public/pages/slo_details/translations.ts new file mode 100644 index 0000000000000..d35784350834a --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_details/translations.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const SLO_DETAILS_PAGE_TITLE = i18n.translate('xpack.observability.sloDetailsPageTitle', { + defaultMessage: 'SLO Details', +}); + +export const SLO_DETAILS_BREADCRUMB_TEXT = i18n.translate( + 'xpack.observability.breadcrumbs.sloDetailsLinkText', + { + defaultMessage: 'Details', + } +); diff --git a/x-pack/test/fleet_api_integration/apis/preconfiguration/index.js b/x-pack/plugins/observability/public/pages/slo_details/types.ts similarity index 60% rename from x-pack/test/fleet_api_integration/apis/preconfiguration/index.js rename to x-pack/plugins/observability/public/pages/slo_details/types.ts index 9b97cda898a0e..46aaa1b02f38b 100644 --- a/x-pack/test/fleet_api_integration/apis/preconfiguration/index.js +++ b/x-pack/plugins/observability/public/pages/slo_details/types.ts @@ -5,8 +5,6 @@ * 2.0. */ -export default function loadTests({ loadTestFile }) { - describe('Preconfiguration Endpoints', () => { - loadTestFile(require.resolve('./preconfiguration')); - }); +export interface SloDetailsPathParams { + sloId: string; } diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx index 72ababa52d7da..eabf09ce9be26 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.stories.tsx @@ -12,7 +12,7 @@ import { SloList as Component } from './slo_list'; export default { component: Component, - title: 'app/SLOs/SloList', + title: 'app/SLO/ListPage/SloList', argTypes: {}, }; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx index 2809d780c56d3..93f0f4d24e111 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx @@ -8,14 +8,14 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { useFetchSloList } from '../hooks/use_fetch_slo_list'; +import { useFetchSloList } from '../../../hooks/slo/use_fetch_slo_list'; export function SloList() { - const [isLoading, sloList] = useFetchSloList(); + const { loading, sloList } = useFetchSloList(); return ( - {!isLoading &&
{JSON.stringify(sloList, null, 2)}
}
+ {!loading &&
{JSON.stringify(sloList, null, 2)}
}
); } diff --git a/x-pack/plugins/observability/public/pages/slos/index.test.tsx b/x-pack/plugins/observability/public/pages/slos/index.test.tsx index 1702cee84311b..2a4bcbbe31c03 100644 --- a/x-pack/plugins/observability/public/pages/slos/index.test.tsx +++ b/x-pack/plugins/observability/public/pages/slos/index.test.tsx @@ -14,14 +14,14 @@ import { useKibana } from '../../utils/kibana_react'; import { kibanaStartMock } from '../../utils/kibana_react.mock'; import { render } from '../../utils/test_helper'; import { SlosPage } from '.'; -import { useFetchSloList } from './hooks/use_fetch_slo_list'; +import { useFetchSloList } from '../../hooks/slo/use_fetch_slo_list'; import { emptySloList } from './mocks/slo_list'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: jest.fn(), })); -jest.mock('./hooks/use_fetch_slo_list'); +jest.mock('../../hooks/slo/use_fetch_slo_list'); jest.mock('../../utils/kibana_react'); jest.mock('../../hooks/use_breadcrumbs'); @@ -50,7 +50,7 @@ describe('SLOs Page', () => { beforeEach(() => { jest.clearAllMocks(); mockKibana(); - useFetchSloListMock.mockReturnValue([false, emptySloList]); + useFetchSloListMock.mockReturnValue({ loading: false, sloList: emptySloList }); }); it('renders the not found page when the feature flag is not enabled', async () => { diff --git a/x-pack/plugins/observability/public/pages/slos/mocks/slo_list.ts b/x-pack/plugins/observability/public/pages/slos/mocks/slo_list.ts index c2a706057ad90..ff54f0e0a05f7 100644 --- a/x-pack/plugins/observability/public/pages/slos/mocks/slo_list.ts +++ b/x-pack/plugins/observability/public/pages/slos/mocks/slo_list.ts @@ -13,3 +13,33 @@ export const emptySloList: SLOList = { perPage: 25, total: 0, }; + +export const sloList: SLOList = { + results: [ + { + id: '1f1c6ee7-433f-4b56-b727-5682262e0d7d', + name: 'latency', + objective: { target: 0.98 }, + summary: { + sliValue: 0.99872, + errorBudget: { + remaining: 0.936, + }, + }, + }, + { + id: 'c0f8d669-9177-4706-9098-f397a88173a6', + name: 'availability', + objective: { target: 0.98 }, + summary: { + sliValue: 0.97, + errorBudget: { + remaining: 0, + }, + }, + }, + ], + page: 1, + perPage: 25, + total: 2, +}; diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 920c3c282ddf2..8303a50f967c5 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { BehaviorSubject, from } from 'rxjs'; import { map } from 'rxjs/operators'; +import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import { AppDeepLink, AppMountParameters, @@ -38,6 +39,7 @@ import { } from '@kbn/triggers-actions-ui-plugin/public'; import { SecurityPluginStart } from '@kbn/security-plugin/public'; import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; +import { RuleDetailsLocatorDefinition } from './locators/rule_details'; import { observabilityAppId, observabilityFeatureId, casesPath } from '../common'; import { createLazyObservabilityPageTemplate } from './components/shared'; import { registerDataHandler } from './data_handler'; @@ -78,6 +80,7 @@ export type ObservabilityPublicSetup = ReturnType; export interface ObservabilityPublicPluginsSetup { data: DataPublicPluginSetup; + share: SharePluginSetup; triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; home?: HomePublicPluginSetup; usageCollection: UsageCollectionSetup; @@ -88,6 +91,7 @@ export interface ObservabilityPublicPluginsStart { cases: CasesUiStart; embeddable: EmbeddableStart; home?: HomePublicPluginStart; + share: SharePluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; @@ -171,6 +175,7 @@ export class Plugin this.observabilityRuleTypeRegistry = createObservabilityRuleTypeRegistry( pluginsSetup.triggersActionsUi.ruleTypeRegistry ); + pluginsSetup.share.url.locators.create(new RuleDetailsLocatorDefinition()); const mount = async (params: AppMountParameters) => { // Load application bundle diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index 98dd2b6c70d7b..20cf331842a55 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -21,6 +21,7 @@ import { AlertingPages } from '../config'; import { AlertDetails } from '../pages/alert_details'; import { DatePickerContextProvider } from '../context/date_picker_context'; import { SlosPage } from '../pages/slos'; +import { SloDetailsPage } from '../pages/slo_details'; export type RouteParams = DecodeParams; @@ -137,4 +138,11 @@ export const routes = { params: {}, exact: true, }, + '/slos/:sloId': { + handler: () => { + return ; + }, + params: {}, + exact: true, + }, }; diff --git a/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts b/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts index a32a97f1f1479..f34b26fc92ccc 100644 --- a/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts +++ b/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts @@ -101,11 +101,11 @@ describe('KibanaSavedObjectsSLORepository', () => { describe('find', () => { const DEFAULT_PAGINATION = { page: 1, perPage: 25 }; - it('includes the filter on name when provided', async () => { + it('includes the filter on name with wildcard when provided', async () => { const repository = new KibanaSavedObjectsSLORepository(soClientMock); soClientMock.find.mockResolvedValueOnce(aFindResponse(SOME_SLO)); - const result = await repository.find({ name: 'availability' }, DEFAULT_PAGINATION); + const result = await repository.find({ name: 'availability*' }, DEFAULT_PAGINATION); expect(result).toEqual({ page: 1, @@ -117,7 +117,27 @@ describe('KibanaSavedObjectsSLORepository', () => { type: SO_SLO_TYPE, page: 1, perPage: 25, - filter: `slo.attributes.name: availability`, + filter: `slo.attributes.name: availability*`, + }); + }); + + it('includes the filter on name with added wildcard when not provided', async () => { + const repository = new KibanaSavedObjectsSLORepository(soClientMock); + soClientMock.find.mockResolvedValueOnce(aFindResponse(SOME_SLO)); + + const result = await repository.find({ name: 'availa' }, DEFAULT_PAGINATION); + + expect(result).toEqual({ + page: 1, + perPage: 25, + total: 1, + results: [SOME_SLO], + }); + expect(soClientMock.find).toHaveBeenCalledWith({ + type: SO_SLO_TYPE, + page: 1, + perPage: 25, + filter: `slo.attributes.name: availa*`, }); }); diff --git a/x-pack/plugins/observability/server/services/slo/slo_repository.ts b/x-pack/plugins/observability/server/services/slo/slo_repository.ts index 12cd3be1c28d1..79edbce1fc751 100644 --- a/x-pack/plugins/observability/server/services/slo/slo_repository.ts +++ b/x-pack/plugins/observability/server/services/slo/slo_repository.ts @@ -96,7 +96,7 @@ export class KibanaSavedObjectsSLORepository implements SLORepository { function buildFilterKuery(criteria: Criteria): string | undefined { const filters: string[] = []; if (!!criteria.name) { - filters.push(`slo.attributes.name: ${criteria.name}`); + filters.push(`slo.attributes.name: ${addWildcardIfAbsent(criteria.name)}`); } return filters.length > 0 ? filters.join(' and ') : undefined; } @@ -113,3 +113,9 @@ function toSLO(storedSLO: StoredSLO): SLO { }, t.identity) ); } + +const WILDCARD_CHAR = '*'; +function addWildcardIfAbsent(value: string): string { + if (value.substring(value.length - 1) === WILDCARD_CHAR) return value; + return `${value}${WILDCARD_CHAR}`; +} diff --git a/x-pack/plugins/profiling/server/index.ts b/x-pack/plugins/profiling/server/index.ts index 26858b770b736..2632f8c9ebaf7 100644 --- a/x-pack/plugins/profiling/server/index.ts +++ b/x-pack/plugins/profiling/server/index.ts @@ -11,9 +11,16 @@ import { ProfilingPlugin } from './plugin'; const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: false }), + elasticsearch: schema.maybe( + schema.object({ + hosts: schema.string(), + username: schema.string(), + password: schema.string(), + }) + ), }); -type ProfilingConfig = TypeOf; +export type ProfilingConfig = TypeOf; // plugin config export const config: PluginConfigDescriptor = { diff --git a/x-pack/plugins/profiling/server/plugin.ts b/x-pack/plugins/profiling/server/plugin.ts index 40071b54c0c4e..923f87a87267d 100644 --- a/x-pack/plugins/profiling/server/plugin.ts +++ b/x-pack/plugins/profiling/server/plugin.ts @@ -6,7 +6,7 @@ */ import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from '@kbn/core/server'; - +import { ProfilingConfig } from '.'; import { PROFILING_FEATURE } from './feature'; import { registerRoutes } from './routes'; import { @@ -16,6 +16,7 @@ import { ProfilingPluginStartDeps, ProfilingRequestHandlerContext, } from './types'; +import { createProfilingEsClient } from './utils/create_profiling_es_client'; export class ProfilingPlugin implements @@ -28,7 +29,8 @@ export class ProfilingPlugin { private readonly logger: Logger; - constructor(initializerContext: PluginInitializerContext) { + constructor(private readonly initializerContext: PluginInitializerContext) { + this.initializerContext = initializerContext; this.logger = initializerContext.logger.get(); } @@ -38,7 +40,17 @@ export class ProfilingPlugin deps.features.registerKibanaFeature(PROFILING_FEATURE); - core.getStartServices().then(([_, depsStart]) => { + const config = this.initializerContext.config.get(); + + core.getStartServices().then(([coreStart, depsStart]) => { + const profilingSpecificEsClient = config.elasticsearch + ? coreStart.elasticsearch.createClient('profiling', { + hosts: [config.elasticsearch.hosts], + username: config.elasticsearch.username, + password: config.elasticsearch.password, + }) + : undefined; + registerRoutes({ router, logger: this.logger!, @@ -46,6 +58,15 @@ export class ProfilingPlugin start: depsStart, setup: deps, }, + services: { + createProfilingEsClient: ({ request, esClient: defaultEsClient }) => { + const esClient = profilingSpecificEsClient + ? profilingSpecificEsClient.asScoped(request).asInternalUser + : defaultEsClient; + + return createProfilingEsClient({ request, esClient }); + }, + }, }); }); diff --git a/x-pack/plugins/profiling/server/routes/flamechart.ts b/x-pack/plugins/profiling/server/routes/flamechart.ts index 49f0415049ad7..62867174fd27e 100644 --- a/x-pack/plugins/profiling/server/routes/flamechart.ts +++ b/x-pack/plugins/profiling/server/routes/flamechart.ts @@ -11,13 +11,16 @@ import { getRoutePaths } from '../../common'; import { createCalleeTree } from '../../common/callee'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; import { createBaseFlameGraph } from '../../common/flamegraph'; -import { createProfilingEsClient } from '../utils/create_profiling_es_client'; import { withProfilingSpan } from '../utils/with_profiling_span'; import { getClient } from './compat'; import { getExecutablesAndStackTraces } from './get_executables_and_stacktraces'; import { createCommonFilter } from './query'; -export function registerFlameChartSearchRoute({ router, logger }: RouteRegisterParameters) { +export function registerFlameChartSearchRoute({ + router, + logger, + services: { createProfilingEsClient }, +}: RouteRegisterParameters) { const paths = getRoutePaths(); router.get( { diff --git a/x-pack/plugins/profiling/server/routes/functions.ts b/x-pack/plugins/profiling/server/routes/functions.ts index 9d3eed222b908..36798b5f31711 100644 --- a/x-pack/plugins/profiling/server/routes/functions.ts +++ b/x-pack/plugins/profiling/server/routes/functions.ts @@ -10,7 +10,6 @@ import { RouteRegisterParameters } from '.'; import { getRoutePaths } from '../../common'; import { createTopNFunctions } from '../../common/functions'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; -import { createProfilingEsClient } from '../utils/create_profiling_es_client'; import { withProfilingSpan } from '../utils/with_profiling_span'; import { getClient } from './compat'; import { getExecutablesAndStackTraces } from './get_executables_and_stacktraces'; @@ -26,7 +25,11 @@ const querySchema = schema.object({ type QuerySchemaType = TypeOf; -export function registerTopNFunctionsSearchRoute({ router, logger }: RouteRegisterParameters) { +export function registerTopNFunctionsSearchRoute({ + router, + logger, + services: { createProfilingEsClient }, +}: RouteRegisterParameters) { const paths = getRoutePaths(); router.get( { diff --git a/x-pack/plugins/profiling/server/routes/index.ts b/x-pack/plugins/profiling/server/routes/index.ts index a3692b213a0af..a9bd74a7e9bf5 100644 --- a/x-pack/plugins/profiling/server/routes/index.ts +++ b/x-pack/plugins/profiling/server/routes/index.ts @@ -6,11 +6,14 @@ */ import type { IRouter, Logger } from '@kbn/core/server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { ProfilingPluginSetupDeps, ProfilingPluginStartDeps, ProfilingRequestHandlerContext, } from '../types'; +import { ProfilingESClient } from '../utils/create_profiling_es_client'; import { registerCacheExecutablesRoute, registerCacheStackFramesRoute } from './cache'; @@ -32,6 +35,12 @@ export interface RouteRegisterParameters { start: ProfilingPluginStartDeps; setup: ProfilingPluginSetupDeps; }; + services: { + createProfilingEsClient: (params: { + request: KibanaRequest; + esClient: ElasticsearchClient; + }) => ProfilingESClient; + }; } export function registerRoutes(params: RouteRegisterParameters) { diff --git a/x-pack/plugins/profiling/server/routes/topn.ts b/x-pack/plugins/profiling/server/routes/topn.ts index a8a7efc01bb52..ea79426a9ffc8 100644 --- a/x-pack/plugins/profiling/server/routes/topn.ts +++ b/x-pack/plugins/profiling/server/routes/topn.ts @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import type { IRouter, Logger } from '@kbn/core/server'; +import type { Logger } from '@kbn/core/server'; import { RouteRegisterParameters } from '.'; import { getRoutePaths, INDEX_EVENTS } from '../../common'; import { ProfilingESField } from '../../common/elasticsearch'; @@ -15,8 +15,7 @@ import { groupStackFrameMetadataByStackTrace, StackTraceID } from '../../common/ import { getFieldNameForTopNType, TopNType } from '../../common/stack_traces'; import { createTopNSamples, getTopNAggregationRequest, TopNResponse } from '../../common/topn'; import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; -import { ProfilingRequestHandlerContext } from '../types'; -import { createProfilingEsClient, ProfilingESClient } from '../utils/create_profiling_es_client'; +import { ProfilingESClient } from '../utils/create_profiling_es_client'; import { withProfilingSpan } from '../utils/with_profiling_span'; import { getClient } from './compat'; import { findDownsampledIndex } from './downsampling'; @@ -156,13 +155,18 @@ export async function topNElasticSearchQuery({ }; } -export function queryTopNCommon( - router: IRouter, - logger: Logger, - pathName: string, - searchField: string, - highCardinality: boolean -) { +export function queryTopNCommon({ + logger, + router, + services: { createProfilingEsClient }, + pathName, + searchField, + highCardinality, +}: RouteRegisterParameters & { + pathName: string; + searchField: string; + highCardinality: boolean; +}) { router.get( { path: pathName, @@ -197,72 +201,52 @@ export function queryTopNCommon( ); } -export function registerTraceEventsTopNContainersSearchRoute({ - router, - logger, -}: RouteRegisterParameters) { +export function registerTraceEventsTopNContainersSearchRoute(parameters: RouteRegisterParameters) { const paths = getRoutePaths(); - return queryTopNCommon( - router, - logger, - paths.TopNContainers, - getFieldNameForTopNType(TopNType.Containers), - false - ); + return queryTopNCommon({ + ...parameters, + pathName: paths.TopNContainers, + searchField: getFieldNameForTopNType(TopNType.Containers), + highCardinality: false, + }); } -export function registerTraceEventsTopNDeploymentsSearchRoute({ - router, - logger, -}: RouteRegisterParameters) { +export function registerTraceEventsTopNDeploymentsSearchRoute(parameters: RouteRegisterParameters) { const paths = getRoutePaths(); - return queryTopNCommon( - router, - logger, - paths.TopNDeployments, - getFieldNameForTopNType(TopNType.Deployments), - false - ); + return queryTopNCommon({ + ...parameters, + pathName: paths.TopNDeployments, + searchField: getFieldNameForTopNType(TopNType.Deployments), + highCardinality: false, + }); } -export function registerTraceEventsTopNHostsSearchRoute({ - router, - logger, -}: RouteRegisterParameters) { +export function registerTraceEventsTopNHostsSearchRoute(parameters: RouteRegisterParameters) { const paths = getRoutePaths(); - return queryTopNCommon( - router, - logger, - paths.TopNHosts, - getFieldNameForTopNType(TopNType.Hosts), - false - ); + return queryTopNCommon({ + ...parameters, + pathName: paths.TopNHosts, + searchField: getFieldNameForTopNType(TopNType.Hosts), + highCardinality: false, + }); } -export function registerTraceEventsTopNStackTracesSearchRoute({ - router, - logger, -}: RouteRegisterParameters) { +export function registerTraceEventsTopNStackTracesSearchRoute(parameters: RouteRegisterParameters) { const paths = getRoutePaths(); - return queryTopNCommon( - router, - logger, - paths.TopNTraces, - getFieldNameForTopNType(TopNType.Traces), - false - ); + return queryTopNCommon({ + ...parameters, + pathName: paths.TopNTraces, + searchField: getFieldNameForTopNType(TopNType.Traces), + highCardinality: false, + }); } -export function registerTraceEventsTopNThreadsSearchRoute({ - router, - logger, -}: RouteRegisterParameters) { +export function registerTraceEventsTopNThreadsSearchRoute(parameters: RouteRegisterParameters) { const paths = getRoutePaths(); - return queryTopNCommon( - router, - logger, - paths.TopNThreads, - getFieldNameForTopNType(TopNType.Threads), - true - ); + return queryTopNCommon({ + ...parameters, + pathName: paths.TopNThreads, + searchField: getFieldNameForTopNType(TopNType.Threads), + highCardinality: true, + }); } diff --git a/x-pack/plugins/reporting/common/constants/index.ts b/x-pack/plugins/reporting/common/constants/index.ts index c27900c1f5a05..7c545aaa48a79 100644 --- a/x-pack/plugins/reporting/common/constants/index.ts +++ b/x-pack/plugins/reporting/common/constants/index.ts @@ -86,6 +86,7 @@ export const ILM_POLICY_NAME = 'kibana-reporting'; // Usage counter types export const API_USAGE_COUNTER_TYPE = 'reportingApi'; +export const API_USAGE_ERROR_TYPE = 'reportingApiError'; // Management UI route export const REPORTING_MANAGEMENT_HOME = '/app/management/insightsAndAlerting/reporting'; diff --git a/x-pack/plugins/reporting/server/routes/deprecations/deprecations.ts b/x-pack/plugins/reporting/server/routes/deprecations/deprecations.ts index 9bbe8e1fb7c4f..ac9cf8be2074b 100644 --- a/x-pack/plugins/reporting/server/routes/deprecations/deprecations.ts +++ b/x-pack/plugins/reporting/server/routes/deprecations/deprecations.ts @@ -5,9 +5,7 @@ * 2.0. */ import { errors } from '@elastic/elasticsearch'; -import type { SecurityHasPrivilegesIndexPrivilegesCheck } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { Logger, RequestHandler } from '@kbn/core/server'; -import { incrementApiUsageCounter } from '..'; import { API_GET_ILM_POLICY_STATUS, API_MIGRATE_ILM_POLICY_URL, @@ -17,6 +15,7 @@ import type { IlmPolicyStatusResponse } from '../../../common/types'; import type { ReportingCore } from '../../core'; import { IlmPolicyManager } from '../../lib'; import { deprecations } from '../../lib/deprecations'; +import { getCounters } from '../lib'; export const registerDeprecationsRoutes = (reporting: ReportingCore, logger: Logger) => { const { router } = reporting.getPluginSetupDeps(); @@ -40,7 +39,7 @@ export const registerDeprecationsRoutes = (reporting: ReportingCore, logger: Log privileges: ['manage'], // required to do anything with the reporting indices names: [store.getReportingIndexPattern()], allow_restricted_indices: true, - } as unknown as SecurityHasPrivilegesIndexPrivilegesCheck, // TODO: Needed until `allow_restricted_indices` is added to the types. + }, ], }, }); @@ -60,7 +59,7 @@ export const registerDeprecationsRoutes = (reporting: ReportingCore, logger: Log router.get( { path: API_GET_ILM_POLICY_STATUS, validate: false }, authzWrapper(async ({ core }, req, res) => { - incrementApiUsageCounter( + const counters = getCounters( req.route.method, API_GET_ILM_POLICY_STATUS, reporting.getUsageCounter() @@ -81,12 +80,17 @@ export const registerDeprecationsRoutes = (reporting: ReportingCore, logger: Log const response: IlmPolicyStatusResponse = { status: await checkIlmMigrationStatus(), }; + + counters.usageCounter(); + return res.ok({ body: response }); } catch (e) { logger.error(e); + const statusCode = e?.statusCode ?? 500; + counters.errorCounter(statusCode); return res.customError({ - statusCode: e?.statusCode ?? 500, body: { message: e.message }, + statusCode, }); } }) @@ -95,7 +99,7 @@ export const registerDeprecationsRoutes = (reporting: ReportingCore, logger: Log router.put( { path: API_MIGRATE_ILM_POLICY_URL, validate: false }, authzWrapper(async ({ core }, req, res) => { - incrementApiUsageCounter( + const counters = getCounters( req.route.method, API_GET_ILM_POLICY_STATUS, reporting.getUsageCounter() @@ -134,6 +138,9 @@ export const registerDeprecationsRoutes = (reporting: ReportingCore, logger: Log }, }, }); + + counters.usageCounter(); + return res.ok(); } catch (err) { logger.error(err); @@ -141,14 +148,18 @@ export const registerDeprecationsRoutes = (reporting: ReportingCore, logger: Log if (err instanceof errors.ResponseError) { // If there were no reporting indices to update, that's OK because then there is nothing to migrate if (err.statusCode === 404) { + counters.errorCounter(undefined, 404); return res.ok(); } + + const statusCode = err.statusCode ?? 500; + counters.errorCounter(undefined, statusCode); return res.customError({ - statusCode: err.statusCode ?? 500, body: { message: err.message, name: err.name, }, + statusCode, }); } diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts index 53046a8680c9b..93a5ab72aff28 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.ts @@ -9,10 +9,9 @@ import type { DocLinksServiceSetup, Logger } from '@kbn/core/server'; import { i18n } from '@kbn/i18n'; import { lastValueFrom } from 'rxjs'; import type { DiagnosticResponse } from '.'; -import { incrementApiUsageCounter } from '..'; import type { ReportingCore } from '../..'; import { API_DIAGNOSE_URL } from '../../../common/constants'; -import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing'; +import { authorizedUserPreRouting, getCounters } from '../lib'; const logsToHelpMapFactory = (docLinks: DocLinksServiceSetup) => ({ 'error while loading shared libraries': i18n.translate( @@ -45,7 +44,8 @@ export const registerDiagnoseBrowser = (reporting: ReportingCore, logger: Logger router.post( { path: `${path}`, validate: {} }, authorizedUserPreRouting(reporting, async (_user, _context, req, res) => { - incrementApiUsageCounter(req.route.method, path, reporting.getUsageCounter()); + const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); + const { docLinks } = reporting.getPluginSetupDeps(); const logsToHelpMap = logsToHelpMapFactory(docLinks); @@ -63,15 +63,19 @@ export const registerDiagnoseBrowser = (reporting: ReportingCore, logger: Logger return helpTexts; }, []); + const success = boundSuccessfully && !help.length; const response: DiagnosticResponse = { - success: boundSuccessfully && !help.length, + success, help, logs, }; + counters.usageCounter(success ? 'success' : 'failure'); + return res.ok({ body: response }); } catch (err) { logger.error(err); + counters.errorCounter(undefined, 500); return res.custom({ statusCode: 500 }); } }) diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts index 9caeadbfa97b9..cedcf65297f75 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/screenshot.ts @@ -5,17 +5,15 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; import type { Logger } from '@kbn/core/server'; -import { lastValueFrom } from 'rxjs'; import { APP_WRAPPER_CLASS } from '@kbn/core/server'; -import { ReportingCore } from '../..'; +import { i18n } from '@kbn/i18n'; +import { lastValueFrom } from 'rxjs'; +import type { ReportingCore } from '../..'; import { API_DIAGNOSE_URL } from '../../../common/constants'; import { generatePngObservable } from '../../export_types/common'; import { getAbsoluteUrlFactory } from '../../export_types/common/get_absolute_url'; -import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing'; -import { DiagnosticResponse } from '.'; -import { incrementApiUsageCounter } from '..'; +import { authorizedUserPreRouting, getCounters } from '../lib'; const path = `${API_DIAGNOSE_URL}/screenshot`; @@ -26,7 +24,7 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log router.post( { path, validate: {} }, authorizedUserPreRouting(reporting, async (_user, _context, req, res) => { - incrementApiUsageCounter(req.route.method, path, reporting.getUsageCounter()); + const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); const config = reporting.getConfig(); const [basePath, protocol, hostname, port] = [ @@ -64,6 +62,8 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log .pipe() ) .then((screenshot) => { + counters.usageCounter(); + // NOTE: the screenshot could be returned as a string using `data:image/png;base64,` + results.buffer.toString('base64') if (screenshot.warnings.length) { return res.ok({ @@ -79,11 +79,12 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log success: true, help: [], logs: '', - } as DiagnosticResponse, + }, }); }) - .catch((error) => - res.ok({ + .catch((error) => { + counters.errorCounter(); + return res.ok({ body: { success: false, help: [ @@ -92,9 +93,9 @@ export const registerDiagnoseScreenshot = (reporting: ReportingCore, logger: Log }), ], logs: error.message, - } as DiagnosticResponse, - }) - ); + }, + }); + }); }) ); }; diff --git a/x-pack/plugins/reporting/server/routes/generate/csv_searchsource_immediate.ts b/x-pack/plugins/reporting/server/routes/generate/csv_searchsource_immediate.ts index 45fe3e863984e..7c4886be207b3 100644 --- a/x-pack/plugins/reporting/server/routes/generate/csv_searchsource_immediate.ts +++ b/x-pack/plugins/reporting/server/routes/generate/csv_searchsource_immediate.ts @@ -5,17 +5,16 @@ * 2.0. */ -import moment from 'moment'; +import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import type { KibanaRequest, Logger } from '@kbn/core/server'; -import { incrementApiUsageCounter } from '..'; +import moment from 'moment'; import type { ReportingCore } from '../..'; import { CSV_SEARCHSOURCE_IMMEDIATE_TYPE } from '../../../common/constants'; import { runTaskFnFactory } from '../../export_types/csv_searchsource_immediate/execute_job'; import type { JobParamsDownloadCSV } from '../../export_types/csv_searchsource_immediate/types'; import { PassThroughStream } from '../../lib'; -import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing'; -import { RequestHandler } from '../lib/request_handler'; +import { authorizedUserPreRouting, getCounters } from '../lib'; const API_BASE_URL_V1 = '/api/reporting/v1'; const API_BASE_GENERATE_V1 = `${API_BASE_URL_V1}/generate`; @@ -71,11 +70,10 @@ export function registerGenerateCsvFromSavedObjectImmediate( authorizedUserPreRouting( reporting, async (user, context, req: CsvFromSavedObjectRequest, res) => { - incrementApiUsageCounter(req.route.method, path, reporting.getUsageCounter()); + const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); const logger = parentLogger.get(CSV_SEARCHSOURCE_IMMEDIATE_TYPE); const runTaskFn = runTaskFnFactory(reporting, logger); - const requestHandler = new RequestHandler(reporting, user, context, req, res, logger); const stream = new PassThroughStream(); const eventLog = reporting.getEventLogger({ jobtype: CSV_SEARCHSOURCE_IMMEDIATE_TYPE, @@ -108,6 +106,8 @@ export function registerGenerateCsvFromSavedObjectImmediate( taskPromise.catch(logError); + counters.usageCounter(); + return res.ok({ body: stream, headers: { @@ -118,7 +118,21 @@ export function registerGenerateCsvFromSavedObjectImmediate( } catch (error) { logError(error); - return requestHandler.handleError(error); + if (error instanceof Boom.Boom) { + const statusCode = error.output.statusCode; + counters.errorCounter(undefined, statusCode); + + return res.customError({ + statusCode, + body: error.output.payload.message, + }); + } + + counters.errorCounter(undefined, 500); + + return res.customError({ + statusCode: 500, + }); } } ) diff --git a/x-pack/plugins/reporting/server/routes/generate/generate_from_jobparams.ts b/x-pack/plugins/reporting/server/routes/generate/generate_from_jobparams.ts index a76058315f185..373e72efb8bd0 100644 --- a/x-pack/plugins/reporting/server/routes/generate/generate_from_jobparams.ts +++ b/x-pack/plugins/reporting/server/routes/generate/generate_from_jobparams.ts @@ -6,14 +6,12 @@ */ import { schema } from '@kbn/config-schema'; -import rison from '@kbn/rison'; import type { Logger } from '@kbn/core/server'; +import rison from '@kbn/rison'; import type { ReportingCore } from '../..'; import { API_BASE_URL } from '../../../common/constants'; import type { BaseParams } from '../../types'; -import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing'; -import { RequestHandler } from '../lib/request_handler'; -import { incrementApiUsageCounter } from '..'; +import { authorizedUserPreRouting, getCounters, RequestHandler } from '../lib'; const BASE_GENERATE = `${API_BASE_URL}/generate`; @@ -41,7 +39,7 @@ export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Lo options: { tags: kibanaAccessControlTags }, }, authorizedUserPreRouting(reporting, async (user, context, req, res) => { - incrementApiUsageCounter( + const counters = getCounters( req.route.method, path.replace(/{exportType}/, req.params.exportType), reporting.getUsageCounter() @@ -86,12 +84,11 @@ export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Lo } const requestHandler = new RequestHandler(reporting, user, context, req, res, logger); - - try { - return await requestHandler.handleGenerateRequest(req.params.exportType, jobParams); - } catch (err) { - return requestHandler.handleError(err); - } + return await requestHandler.handleGenerateRequest( + req.params.exportType, + jobParams, + counters + ); }) ); }; diff --git a/x-pack/plugins/reporting/server/routes/index.ts b/x-pack/plugins/reporting/server/routes/index.ts index f063625cab2fd..570d74f3e02ab 100644 --- a/x-pack/plugins/reporting/server/routes/index.ts +++ b/x-pack/plugins/reporting/server/routes/index.ts @@ -6,8 +6,6 @@ */ import type { Logger } from '@kbn/core/server'; -import { UsageCounter } from '@kbn/usage-collection-plugin/server'; -import { API_USAGE_COUNTER_TYPE } from '../../common/constants'; import { ReportingCore } from '..'; import { registerDeprecationsRoutes } from './deprecations/deprecations'; import { registerDiagnosticRoutes } from './diagnostic'; @@ -17,17 +15,6 @@ import { } from './generate'; import { registerJobInfoRoutes } from './management'; -export function incrementApiUsageCounter( - method: string, - path: string, - usageCounter: UsageCounter | undefined -) { - usageCounter?.incrementCounter({ - counterName: `${method} ${path}`, - counterType: API_USAGE_COUNTER_TYPE, - }); -} - export function registerRoutes(reporting: ReportingCore, logger: Logger) { registerDeprecationsRoutes(reporting, logger); registerDiagnosticRoutes(reporting, logger); diff --git a/x-pack/plugins/reporting/server/routes/lib/get_counter.ts b/x-pack/plugins/reporting/server/routes/lib/get_counter.ts new file mode 100644 index 0000000000000..2888bebcc5e6b --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/lib/get_counter.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UsageCounter } from '@kbn/usage-collection-plugin/server'; +import { API_USAGE_COUNTER_TYPE, API_USAGE_ERROR_TYPE } from '../../../common/constants'; + +export type Counters = ReturnType; + +/** + * A helper utility that can be passed around and call the usage counter service + */ +export function getCounters(method: string, path: string, usageCounter: UsageCounter | undefined) { + return { + /** + * constructs a counterName from the API request method and path + * appends an optional "path suffix" for additional context about filetype, etc + */ + usageCounter(pathSuffix?: string) { + const counterName = `${method} ${path}${pathSuffix ? ':' + pathSuffix : ''}`; + + usageCounter?.incrementCounter({ + counterName, + counterType: API_USAGE_COUNTER_TYPE, + }); + }, + + /** + * appends `:{statusCode}` to the counterName if there is a statusCode + */ + errorCounter(pathSuffix?: string, statusCode?: number) { + let counterName = `${method} ${path}`; + if (pathSuffix) { + counterName += `:${pathSuffix}`; + } + if (statusCode) { + counterName += `:${statusCode}`; + } + + usageCounter?.incrementCounter({ + counterName, + counterType: API_USAGE_ERROR_TYPE, + }); + }, + }; +} diff --git a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts index 3840905f73c8b..52dec4467a7a0 100644 --- a/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -19,7 +19,7 @@ export interface ErrorFromPayload { } // interface of the API result -interface Payload { +export interface Payload { statusCode: number; content: string | Stream | ErrorFromPayload; contentType: string | null; diff --git a/x-pack/plugins/reporting/server/routes/lib/index.ts b/x-pack/plugins/reporting/server/routes/lib/index.ts new file mode 100644 index 0000000000000..50f5653f894ff --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/lib/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { authorizedUserPreRouting } from './authorized_user_pre_routing'; +export { jobManagementPreRouting } from './job_management_pre_routing'; + +export { getCounters } from './get_counter'; +export type { Counters } from './get_counter'; + +export { handleUnavailable, RequestHandler } from './request_handler'; +export { jobsQueryFactory } from './jobs_query'; diff --git a/x-pack/plugins/reporting/server/routes/lib/job_management_pre_routing.test.ts b/x-pack/plugins/reporting/server/routes/lib/job_management_pre_routing.test.ts new file mode 100644 index 0000000000000..3e3bebbc614b1 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/lib/job_management_pre_routing.test.ts @@ -0,0 +1,185 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { httpServerMock } from '@kbn/core/server/mocks'; +import { ReportingCore } from '../..'; +import { ReportingInternalSetup, ReportingInternalStart } from '../../core'; +import { + createMockConfigSchema, + createMockPluginSetup, + createMockPluginStart, + createMockReportingCore, +} from '../../test_helpers'; +import { jobsQueryFactory } from './jobs_query'; +import { jobManagementPreRouting } from './job_management_pre_routing'; + +jest.mock('../../lib/content_stream'); +jest.mock('./jobs_query'); + +const mockReportingConfig = createMockConfigSchema({ roles: { enabled: false } }); +let mockCore: ReportingCore; +let mockSetupDeps: ReportingInternalSetup; +let mockStartDeps: ReportingInternalStart; +const mockJobsQueryFactory = jobsQueryFactory as jest.Mocked; +const mockResponseFactory = httpServerMock.createResponseFactory(); +const mockCounters = { + usageCounter: jest.fn(), + errorCounter: jest.fn(), +}; +const mockUser = { username: 'joeuser' }; + +beforeEach(async () => { + mockSetupDeps = createMockPluginSetup({ + security: { license: { isEnabled: () => true } }, + }); + + mockStartDeps = await createMockPluginStart( + { + security: { + authc: { + getCurrentUser: () => ({ id: '123', roles: ['superuser'], username: 'Tom Riddle' }), + }, + }, + }, + mockReportingConfig + ); + mockCore = await createMockReportingCore(mockReportingConfig, mockSetupDeps, mockStartDeps); +}); + +it(`should return 404 if the docId isn't resolve`, async function () { + mockJobsQueryFactory.mockReturnValue({ + get: jest.fn(), + }); + + let handlerCalled = false; + const handler = async () => { + handlerCalled = true; + return { + status: 200, + options: {}, + }; + }; + + await jobManagementPreRouting( + mockCore, + mockResponseFactory, + 'doc123', + mockUser, + mockCounters, + handler + ); + + expect(mockResponseFactory.notFound).toBeCalled(); + expect(handlerCalled).toBe(false); +}); + +it(`should return forbidden if job type is unrecognized`, async function () { + mockJobsQueryFactory.mockReturnValue({ + get: jest.fn(() => ({ jobtype: 'notARealJobType' })), + }); + + let handlerCalled = false; + const handler = async () => { + handlerCalled = true; + return { + status: 200, + options: {}, + }; + }; + + await jobManagementPreRouting( + mockCore, + mockResponseFactory, + 'doc123', + mockUser, + mockCounters, + handler + ); + + expect(mockResponseFactory.forbidden).toBeCalled(); + expect(handlerCalled).toBe(false); +}); + +it(`should call callback when document is available`, async function () { + mockJobsQueryFactory.mockReturnValue({ + get: jest.fn(() => ({ jobtype: 'csv_searchsource' })), + }); + + let handlerCalled = false; + const handler = async () => { + handlerCalled = true; + return { + status: 200, + options: {}, + }; + }; + + await jobManagementPreRouting( + mockCore, + mockResponseFactory, + 'doc123', + mockUser, + mockCounters, + handler + ); + + expect(handlerCalled).toBe(true); +}); + +describe('usage counters', () => { + beforeEach(() => { + mockCounters.usageCounter.mockReset(); + mockCounters.errorCounter.mockReset(); + }); + + it(`should track valid usage`, async function () { + mockJobsQueryFactory.mockReturnValue({ + get: jest.fn(() => ({ jobtype: 'csv_searchsource' })), + }); + + const handler = async () => ({ + status: 200, + options: {}, + }); + + expect(mockCounters.usageCounter).not.toBeCalled(); + + await jobManagementPreRouting( + mockCore, + mockResponseFactory, + 'doc123', + mockUser, + mockCounters, + handler + ); + + expect(mockCounters.usageCounter).toBeCalled(); + }); + + it(`should track error case`, async function () { + mockJobsQueryFactory.mockReturnValue({ + get: jest.fn(() => ({ jobtype: 'csv_searchsource' })), + }); + + const handler = async () => { + throw new Error(`this error is a test`); + }; + + expect(mockCounters.errorCounter).not.toBeCalled(); + + await jobManagementPreRouting( + mockCore, + mockResponseFactory, + 'doc123', + mockUser, + mockCounters, + handler + ); + + expect(mockCounters.errorCounter).toBeCalled(); + }); +}); diff --git a/x-pack/plugins/reporting/server/routes/lib/job_management_pre_routing.ts b/x-pack/plugins/reporting/server/routes/lib/job_management_pre_routing.ts new file mode 100644 index 0000000000000..a443b6fc05bb3 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/lib/job_management_pre_routing.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Boom from '@hapi/boom'; +import { IKibanaResponse, kibanaResponseFactory } from '@kbn/core/server'; +import { i18n } from '@kbn/i18n'; +import { jobsQueryFactory } from '.'; +import { ReportingCore } from '../..'; +import { ReportApiJSON } from '../../lib/store/report'; +import { ReportingUser } from '../../types'; +import type { Counters } from './get_counter'; + +/** + * The body of a route handler to call via callback + */ +type JobManagementResponseHandler = (doc: ReportApiJSON) => Promise>; + +/** + * Handles the common parts of requests to manage (view, download and delete) reports + */ +export const jobManagementPreRouting = async ( + reporting: ReportingCore, + res: typeof kibanaResponseFactory, + docId: string, + user: ReportingUser, + counters: Counters, + cb: JobManagementResponseHandler +) => { + const licenseInfo = await reporting.getLicenseInfo(); + const { + management: { jobTypes = [] }, + } = licenseInfo; + + const jobsQuery = jobsQueryFactory(reporting); + + const doc = await jobsQuery.get(user, docId); + if (!doc) { + return res.notFound(); + } + + const { jobtype } = doc; + if (!jobTypes.includes(jobtype)) { + return res.forbidden({ + body: i18n.translate('xpack.reporting.jobResponse.errorHandler.notAuthorized', { + defaultMessage: `Sorry, you are not authorized to view or delete {jobtype} reports`, + values: { jobtype }, + }), + }); + } + + // Count usage once allowing the request + counters.usageCounter(jobtype); + + try { + return await cb(doc); + } catch (err) { + const { logger } = reporting.getPluginSetupDeps(); + logger.error(err); + if (err instanceof Boom.Boom) { + const statusCode = err.output.statusCode; + counters?.errorCounter(jobtype, statusCode); + return res.customError({ + statusCode, + body: err.output.payload.message, + }); + } + + counters?.errorCounter(jobtype, 500); + return res.customError({ + statusCode: 500, + body: + err?.message || + i18n.translate('xpack.reporting.jobResponse.errorHandler.unknownError', { + defaultMessage: 'Unknown error', + }), + }); + } +}; diff --git a/x-pack/plugins/reporting/server/routes/lib/job_response_handler.test.ts b/x-pack/plugins/reporting/server/routes/lib/job_response_handler.test.ts deleted file mode 100644 index 5bf87885dc431..0000000000000 --- a/x-pack/plugins/reporting/server/routes/lib/job_response_handler.test.ts +++ /dev/null @@ -1,215 +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 { Readable, Writable } from 'stream'; -import { kibanaResponseFactory } from '@kbn/core/server'; -import { CSV_JOB_TYPE, PDF_JOB_TYPE } from '../../../common/constants'; -import { ReportingCore } from '../..'; -import { ContentStream, getContentStream } from '../../lib'; -import { createMockConfigSchema, createMockReportingCore } from '../../test_helpers'; -import { jobsQueryFactory } from './jobs_query'; -import { getDocumentPayloadFactory } from './get_document_payload'; -import { deleteJobResponseHandler, downloadJobResponseHandler } from './job_response_handler'; - -jest.mock('../../lib/content_stream'); -jest.mock('./get_document_payload'); -jest.mock('./jobs_query'); - -let core: ReportingCore; -let getDocumentPayload: jest.MockedFunction>; -let jobsQuery: jest.Mocked>; -let response: jest.Mocked; -let write: jest.Mocked; - -beforeEach(async () => { - const schema = createMockConfigSchema(); - core = await createMockReportingCore(schema); - getDocumentPayload = jest.fn(); - jobsQuery = { - delete: jest.fn(), - get: jest.fn(), - } as unknown as typeof jobsQuery; - response = { - badRequest: jest.fn(), - custom: jest.fn(), - customError: jest.fn(), - notFound: jest.fn(), - ok: jest.fn(), - unauthorized: jest.fn(), - } as unknown as typeof response; - write = jest.fn((_chunk, _encoding, callback) => callback()); - - (getContentStream as jest.MockedFunction).mockResolvedValue( - new Writable({ write }) as ContentStream - ); - ( - getDocumentPayloadFactory as jest.MockedFunction - ).mockReturnValue(getDocumentPayload); - (jobsQueryFactory as jest.MockedFunction).mockReturnValue(jobsQuery); -}); - -describe('deleteJobResponseHandler', () => { - it('should return not found response when there is no job', async () => { - jobsQuery.get.mockResolvedValueOnce(undefined); - await deleteJobResponseHandler(core, response, [], { username: 'somebody' }, { docId: 'id' }); - - expect(response.notFound).toHaveBeenCalled(); - }); - - it('should return unauthorized response when the job type is not valid', async () => { - jobsQuery.get.mockResolvedValueOnce({ jobtype: PDF_JOB_TYPE } as Awaited< - ReturnType - >); - await deleteJobResponseHandler( - core, - response, - [CSV_JOB_TYPE], - { username: 'somebody' }, - { docId: 'id' } - ); - - expect(response.unauthorized).toHaveBeenCalledWith({ body: expect.any(String) }); - }); - - it('should delete existing job', async () => { - jobsQuery.get.mockResolvedValueOnce({ - jobtype: PDF_JOB_TYPE, - index: '.reporting-12345', - } as Awaited>); - await deleteJobResponseHandler( - core, - response, - [PDF_JOB_TYPE], - { username: 'somebody' }, - { docId: 'id' } - ); - - expect(write).toHaveBeenCalledWith(Buffer.from(''), expect.anything(), expect.anything()); - expect(jobsQuery.delete).toHaveBeenCalledWith('.reporting-12345', 'id'); - expect(response.ok).toHaveBeenCalledWith({ body: { deleted: true } }); - }); - - it('should return a custom error on exception', async () => { - jobsQuery.get.mockResolvedValueOnce({ jobtype: PDF_JOB_TYPE } as Awaited< - ReturnType - >); - jobsQuery.delete.mockRejectedValueOnce( - Object.assign(new Error('Some error.'), { statusCode: 123 }) - ); - await deleteJobResponseHandler( - core, - response, - [PDF_JOB_TYPE], - { username: 'somebody' }, - { docId: 'id' } - ); - - expect(response.customError).toHaveBeenCalledWith({ - statusCode: 123, - body: 'Some error.', - }); - }); -}); - -describe('downloadJobResponseHandler', () => { - it('should return not found response when there is no job', async () => { - jobsQuery.get.mockResolvedValueOnce(undefined); - await downloadJobResponseHandler(core, response, [], { username: 'somebody' }, { docId: 'id' }); - - expect(response.notFound).toHaveBeenCalled(); - }); - - it('should return unauthorized response when the job type is not valid', async () => { - jobsQuery.get.mockResolvedValueOnce({ jobtype: PDF_JOB_TYPE } as Awaited< - ReturnType - >); - await downloadJobResponseHandler( - core, - response, - [CSV_JOB_TYPE], - { username: 'somebody' }, - { docId: 'id' } - ); - - expect(response.unauthorized).toHaveBeenCalledWith({ body: expect.any(String) }); - }); - - it('should return bad request response when the job content type is not allowed', async () => { - jobsQuery.get.mockResolvedValueOnce({ jobtype: PDF_JOB_TYPE } as Awaited< - ReturnType - >); - getDocumentPayload.mockResolvedValueOnce({ - contentType: 'image/jpeg', - } as unknown as Awaited>); - await downloadJobResponseHandler( - core, - response, - [PDF_JOB_TYPE], - { username: 'somebody' }, - { docId: 'id' } - ); - - expect(response.badRequest).toHaveBeenCalledWith({ body: expect.any(String) }); - }); - - it('should return custom response with payload contents', async () => { - jobsQuery.get.mockResolvedValueOnce({ jobtype: PDF_JOB_TYPE } as Awaited< - ReturnType - >); - getDocumentPayload.mockResolvedValueOnce({ - content: new Readable(), - contentType: 'application/pdf', - headers: { - 'Content-Length': 10, - }, - statusCode: 200, - } as unknown as Awaited>); - await downloadJobResponseHandler( - core, - response, - [PDF_JOB_TYPE], - { username: 'somebody' }, - { docId: 'id' } - ); - - expect(response.custom).toHaveBeenCalledWith({ - body: expect.any(Readable), - statusCode: 200, - headers: expect.objectContaining({ - 'Content-Length': 10, - 'content-type': 'application/pdf', - }), - }); - }); - - it('should return custom response with error message', async () => { - jobsQuery.get.mockResolvedValueOnce({ jobtype: PDF_JOB_TYPE } as Awaited< - ReturnType - >); - getDocumentPayload.mockResolvedValueOnce({ - content: 'Error message.', - contentType: 'application/json', - headers: {}, - statusCode: 500, - } as unknown as Awaited>); - await downloadJobResponseHandler( - core, - response, - [PDF_JOB_TYPE], - { username: 'somebody' }, - { docId: 'id' } - ); - - expect(response.custom).toHaveBeenCalledWith({ - body: Buffer.from('Error message.'), - statusCode: 500, - headers: expect.objectContaining({ - 'content-type': 'application/json', - }), - }); - }); -}); diff --git a/x-pack/plugins/reporting/server/routes/lib/job_response_handler.ts b/x-pack/plugins/reporting/server/routes/lib/job_response_handler.ts deleted file mode 100644 index fb5edc6b25f71..0000000000000 --- a/x-pack/plugins/reporting/server/routes/lib/job_response_handler.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { promisify } from 'util'; -import { kibanaResponseFactory } from '@kbn/core/server'; -import { ReportingCore } from '../..'; -import { ALLOWED_JOB_CONTENT_TYPES } from '../../../common/constants'; -import { getContentStream } from '../../lib'; -import { ReportingUser } from '../../types'; -import { getDocumentPayloadFactory } from './get_document_payload'; -import { jobsQueryFactory } from './jobs_query'; - -interface JobResponseHandlerParams { - docId: string; -} - -export async function downloadJobResponseHandler( - reporting: ReportingCore, - res: typeof kibanaResponseFactory, - validJobTypes: string[], - user: ReportingUser, - params: JobResponseHandlerParams -) { - const jobsQuery = jobsQueryFactory(reporting); - const getDocumentPayload = getDocumentPayloadFactory(reporting); - try { - const { docId } = params; - - const doc = await jobsQuery.get(user, docId); - if (!doc) { - return res.notFound(); - } - - if (!validJobTypes.includes(doc.jobtype)) { - return res.unauthorized({ - body: `Sorry, you are not authorized to download ${doc.jobtype} reports`, - }); - } - - const payload = await getDocumentPayload(doc); - - if (!payload.contentType || !ALLOWED_JOB_CONTENT_TYPES.includes(payload.contentType)) { - return res.badRequest({ - body: `Unsupported content-type of ${payload.contentType} specified by job output`, - }); - } - - return res.custom({ - body: typeof payload.content === 'string' ? Buffer.from(payload.content) : payload.content, - statusCode: payload.statusCode, - headers: { - ...payload.headers, - 'content-type': payload.contentType, - }, - }); - } catch (err) { - const { logger } = reporting.getPluginSetupDeps(); - logger.error(err); - throw err; - } -} - -export async function deleteJobResponseHandler( - reporting: ReportingCore, - res: typeof kibanaResponseFactory, - validJobTypes: string[], - user: ReportingUser, - params: JobResponseHandlerParams -) { - const jobsQuery = jobsQueryFactory(reporting); - - const { docId } = params; - const doc = await jobsQuery.get(user, docId); - - if (!doc) { - return res.notFound(); - } - - const { jobtype: jobType } = doc; - - if (!validJobTypes.includes(jobType)) { - return res.unauthorized({ - body: `Sorry, you are not authorized to delete ${jobType} reports`, - }); - } - - const docIndex = doc.index; - const stream = await getContentStream(reporting, { id: docId, index: docIndex }); - - try { - /** @note Overwriting existing content with an empty buffer to remove all the chunks. */ - await promisify(stream.end.bind(stream, '', 'utf8'))(); - await jobsQuery.delete(docIndex, docId); - return res.ok({ - body: { deleted: true }, - }); - } catch (error) { - return res.customError({ - statusCode: error.statusCode, - body: error.message, - }); - } -} diff --git a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts index a0022cb107015..dee61b41de7be 100644 --- a/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts +++ b/x-pack/plugins/reporting/server/routes/lib/jobs_query.ts @@ -6,27 +6,24 @@ */ import type { TransportResult } from '@elastic/elasticsearch'; -import { - DeleteResponse, - SearchHit, - SearchResponse, - SearchRequest, -} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { errors } from '@elastic/elasticsearch'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { ElasticsearchClient } from '@kbn/core/server'; import { i18n } from '@kbn/i18n'; -import { ElasticsearchClient } from '@kbn/core/server'; -import { ReportingCore } from '../..'; +import type { ReportingCore } from '../..'; import { REPORTING_SYSTEM_INDEX } from '../../../common/constants'; -import { ReportApiJSON, ReportSource } from '../../../common/types'; +import type { ReportApiJSON, ReportSource } from '../../../common/types'; import { statuses } from '../../lib/statuses'; import { Report } from '../../lib/store'; -import { ReportingUser } from '../../types'; -import { runtimeFields, runtimeFieldKeys } from '../../lib/store/runtime_fields'; +import { runtimeFieldKeys, runtimeFields } from '../../lib/store/runtime_fields'; +import type { ReportingUser } from '../../types'; +import type { Payload } from './get_document_payload'; +import { getDocumentPayloadFactory } from './get_document_payload'; const defaultSize = 10; const getUsername = (user: ReportingUser) => (user ? user.username : false); -function getSearchBody(body: SearchRequest['body']): SearchRequest['body'] { +function getSearchBody(body: estypes.SearchRequest['body']): estypes.SearchRequest['body'] { return { _source: { excludes: ['output.content', 'payload.headers'], @@ -43,7 +40,7 @@ export type ReportContent = Pick payload?: Pick; }; -interface JobsQueryFactory { +export interface JobsQueryFactory { list( jobTypes: string[], user: ReportingUser, @@ -54,7 +51,8 @@ interface JobsQueryFactory { count(jobTypes: string[], user: ReportingUser): Promise; get(user: ReportingUser, id: string): Promise; getError(id: string): Promise; - delete(deleteIndex: string, id: string): Promise>; + getDocumentPayload(doc: ReportApiJSON): Promise; + delete(deleteIndex: string, id: string): Promise>; } export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory { @@ -101,10 +99,10 @@ export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory const response = (await execQuery((elasticsearchClient) => elasticsearchClient.search({ body, index: getIndex() }) - )) as SearchResponse; + )) as estypes.SearchResponse; return ( - response?.hits?.hits.map((report: SearchHit) => { + response?.hits?.hits.map((report: estypes.SearchHit) => { const { _source: reportSource, ...reportHead } = report; if (!reportSource) { throw new Error(`Search hit did not include _source!`); @@ -174,7 +172,7 @@ export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory }, async getError(id) { - const body: SearchRequest['body'] = { + const body: estypes.SearchRequest['body'] = { _source: { includes: ['output.content', 'status'], }, @@ -203,6 +201,11 @@ export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory return hits?._source?.output?.content!; }, + async getDocumentPayload(doc: ReportApiJSON) { + const getDocumentPayload = getDocumentPayloadFactory(reportingCore); + return await getDocumentPayload(doc); + }, + async delete(deleteIndex, id) { try { const { asInternalUser: elasticsearchClient } = await reportingCore.getEsClient(); diff --git a/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts b/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts index 14f367677321d..69fb16831d557 100644 --- a/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts +++ b/x-pack/plugins/reporting/server/routes/lib/request_handler.test.ts @@ -40,6 +40,19 @@ const getMockResponseFactory = () => } as unknown as KibanaResponseFactory); const mockLogger = loggingSystemMock.createLogger(); +const mockJobParams: JobParamsPDFDeprecated = { + browserTimezone: 'UTC', + objectType: 'cool_object_type', + title: 'cool_title', + version: 'unknown', + layout: { id: 'preserve_layout' }, + relativeUrls: [], +}; + +const mockCounters = { + usageCounter: jest.fn(), + errorCounter: jest.fn(), +}; describe('Handle request to generate', () => { let reportingCore: ReportingCore; @@ -48,15 +61,6 @@ describe('Handle request to generate', () => { let mockResponseFactory: ReturnType; let requestHandler: RequestHandler; - const mockJobParams: JobParamsPDFDeprecated = { - browserTimezone: 'UTC', - objectType: 'cool_object_type', - title: 'cool_title', - version: 'unknown', - layout: { id: 'preserve_layout' }, - relativeUrls: [], - }; - beforeEach(async () => { reportingCore = await createMockReportingCore(createMockConfigSchema({})); reportingCore.getStore = () => @@ -149,7 +153,7 @@ describe('Handle request to generate', () => { }); test('disallows invalid export type', async () => { - expect(await requestHandler.handleGenerateRequest('neanderthals', mockJobParams)) + expect(await requestHandler.handleGenerateRequest('neanderthals', mockJobParams, mockCounters)) .toMatchInlineSnapshot(` Object { "body": "Invalid export-type of neanderthals", @@ -165,8 +169,9 @@ describe('Handle request to generate', () => { }, })); - expect(await requestHandler.handleGenerateRequest('csv_searchsource', mockJobParams)) - .toMatchInlineSnapshot(` + expect( + await requestHandler.handleGenerateRequest('csv_searchsource', mockJobParams, mockCounters) + ).toMatchInlineSnapshot(` Object { "body": "seeing this means the license isn't supported", } @@ -182,10 +187,14 @@ describe('Handle request to generate', () => { })); expect( - await requestHandler.handleGenerateRequest('csv_searchsource', { - ...mockJobParams, - browserTimezone: 'America/Amsterdam', - }) + await requestHandler.handleGenerateRequest( + 'csv_searchsource', + { + ...mockJobParams, + browserTimezone: 'America/Amsterdam', + }, + mockCounters + ) ).toMatchInlineSnapshot(` Object { "body": "seeing this means the license isn't supported", @@ -196,7 +205,8 @@ describe('Handle request to generate', () => { test('generates the download path', async () => { const response = (await requestHandler.handleGenerateRequest( 'csv_searchsource', - mockJobParams + mockJobParams, + mockCounters )) as unknown as { body: { job: ReportApiJSON } }; const { id, created_at: _created_at, ...snapObj } = response.body.job; expect(snapObj).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/reporting/server/routes/lib/request_handler.ts b/x-pack/plugins/reporting/server/routes/lib/request_handler.ts index f6f3e8618f99d..e54121c9dd974 100644 --- a/x-pack/plugins/reporting/server/routes/lib/request_handler.ts +++ b/x-pack/plugins/reporting/server/routes/lib/request_handler.ts @@ -14,6 +14,7 @@ import { API_BASE_URL } from '../../../common/constants'; import { checkParamsVersion, cryptoFactory } from '../../lib'; import { Report } from '../../lib/store'; import type { BaseParams, ReportingRequestHandlerContext, ReportingUser } from '../../types'; +import { Counters } from './get_counter'; export const handleUnavailable = (res: KibanaResponseFactory) => { return res.custom({ statusCode: 503, body: 'Not Available' }); @@ -24,6 +25,9 @@ const getDownloadBaseUrl = (reporting: ReportingCore) => { return config.kbnConfig.get('server', 'basePath') + `${API_BASE_URL}/jobs/download`; }; +/** + * Handles the common parts of requests to generate a report + */ export class RequestHandler { constructor( private reporting: ReportingCore, @@ -106,7 +110,11 @@ export class RequestHandler { return report; } - public async handleGenerateRequest(exportTypeId: string, jobParams: BaseParams) { + public async handleGenerateRequest( + exportTypeId: string, + jobParams: BaseParams, + counters: Counters + ) { // ensure the async dependencies are loaded if (!this.context.reporting) { return handleUnavailable(this.res); @@ -129,12 +137,15 @@ export class RequestHandler { }); } + let report: Report | undefined; try { - const report = await this.enqueueJob(exportTypeId, jobParams); + report = await this.enqueueJob(exportTypeId, jobParams); // return task manager's task information and the download URL const downloadBaseUrl = getDownloadBaseUrl(this.reporting); + counters.usageCounter(); + return this.res.ok({ headers: { 'content-type': 'application/json' }, body: { @@ -143,23 +154,25 @@ export class RequestHandler { }, }); } catch (err) { - this.logger.error(err); - throw err; + return this.handleError(err, counters, report?.jobtype); } } - /* - * This method does not log the error, as it assumes the error has already - * been caught and logged for stack trace context, and then rethrown - */ - public handleError(err: Error | Boom.Boom) { + private handleError(err: Error | Boom.Boom, counters: Counters, jobtype?: string) { + this.logger.error(err); + if (err instanceof Boom.Boom) { + const statusCode = err.output.statusCode; + counters?.errorCounter(jobtype, statusCode); + return this.res.customError({ - statusCode: err.output.statusCode, + statusCode, body: err.output.payload.message, }); } + counters?.errorCounter(jobtype, 500); + return this.res.customError({ statusCode: 500, body: diff --git a/x-pack/plugins/reporting/server/routes/management/integration_tests/jobs.test.ts b/x-pack/plugins/reporting/server/routes/management/integration_tests/jobs.test.ts index dce551209c05f..9f6e3e0fe235b 100644 --- a/x-pack/plugins/reporting/server/routes/management/integration_tests/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/management/integration_tests/jobs.test.ts @@ -161,7 +161,7 @@ describe('GET /api/reporting/jobs/download', () => { await supertest(httpSetup.server.listener).get('/api/reporting/jobs/download/poo').expect(404); }); - it('returns a 401 if not a valid job type', async () => { + it('returns a 403 if not a valid job type', async () => { mockEsClient.search.mockResponseOnce( getHits({ jobtype: 'invalidJobType', @@ -172,7 +172,7 @@ describe('GET /api/reporting/jobs/download', () => { await server.start(); - await supertest(httpSetup.server.listener).get('/api/reporting/jobs/download/poo').expect(401); + await supertest(httpSetup.server.listener).get('/api/reporting/jobs/download/poo').expect(403); }); it(`returns job's info`, async () => { diff --git a/x-pack/plugins/reporting/server/routes/management/jobs.ts b/x-pack/plugins/reporting/server/routes/management/jobs.ts index b09565062034d..8c93f25e80873 100644 --- a/x-pack/plugins/reporting/server/routes/management/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/management/jobs.ts @@ -6,15 +6,18 @@ */ import { schema } from '@kbn/config-schema'; -import { i18n } from '@kbn/i18n'; import { ROUTE_TAG_CAN_REDIRECT } from '@kbn/security-plugin/server'; -import { incrementApiUsageCounter } from '..'; +import { promisify } from 'util'; import { ReportingCore } from '../..'; -import { API_BASE_URL } from '../../../common/constants'; -import { authorizedUserPreRouting } from '../lib/authorized_user_pre_routing'; -import { jobsQueryFactory } from '../lib/jobs_query'; -import { deleteJobResponseHandler, downloadJobResponseHandler } from '../lib/job_response_handler'; -import { handleUnavailable } from '../lib/request_handler'; +import { ALLOWED_JOB_CONTENT_TYPES, API_BASE_URL } from '../../../common/constants'; +import { getContentStream } from '../../lib'; +import { + authorizedUserPreRouting, + getCounters, + handleUnavailable, + jobManagementPreRouting, + jobsQueryFactory, +} from '../lib'; const MAIN_ENTRY = `${API_BASE_URL}/jobs`; @@ -39,7 +42,7 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { }, }, authorizedUserPreRouting(reporting, async (user, context, req, res) => { - incrementApiUsageCounter(req.route.method, path, reporting.getUsageCounter()); + const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); // ensure the async dependencies are loaded if (!context.reporting) { @@ -55,6 +58,8 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { const jobIds = queryIds ? queryIds.split(',') : null; const results = await jobsQuery.list(jobTypes, user, page, size, jobIds); + counters.usageCounter(); + return res.ok({ body: results, headers: { @@ -75,7 +80,7 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { validate: false, }, authorizedUserPreRouting(reporting, async (user, context, req, res) => { - incrementApiUsageCounter(req.route.method, path, reporting.getUsageCounter()); + const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); // ensure the async dependencies are loaded if (!context.reporting) { @@ -88,6 +93,8 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { const count = await jobsQuery.count(jobTypes, user); + counters.usageCounter(); + return res.ok({ body: count.toString(), headers: { @@ -112,7 +119,7 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { }, }, authorizedUserPreRouting(reporting, async (user, context, req, res) => { - incrementApiUsageCounter(req.route.method, path, reporting.getUsageCounter()); + const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); // ensure the async dependencies are loaded if (!context.reporting) { @@ -120,33 +127,14 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { } const { docId } = req.params; - const { - management: { jobTypes = [] }, - } = await reporting.getLicenseInfo(); - - const result = await jobsQuery.get(user, docId); - - if (!result) { - return res.notFound(); - } - - const { jobtype: jobType } = result; - - if (!jobTypes.includes(jobType)) { - return res.forbidden({ - body: i18n.translate('xpack.reporting.jobsQuery.infoError.unauthorizedErrorMessage', { - defaultMessage: 'Sorry, you are not authorized to view {jobType} info', - values: { jobType }, - }), - }); - } - - return res.ok({ - body: result, - headers: { - 'content-type': 'application/json', - }, - }); + return jobManagementPreRouting(reporting, res, docId, user, counters, async (doc) => + res.ok({ + body: doc, + headers: { + 'content-type': 'application/json', + }, + }) + ); }) ); }; @@ -166,7 +154,7 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { options: { tags: [ROUTE_TAG_CAN_REDIRECT] }, }, authorizedUserPreRouting(reporting, async (user, context, req, res) => { - incrementApiUsageCounter(req.route.method, path, reporting.getUsageCounter()); + const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); // ensure the async dependencies are loaded if (!context.reporting) { @@ -174,11 +162,26 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { } const { docId } = req.params; - const { - management: { jobTypes = [] }, - } = await reporting.getLicenseInfo(); - return downloadJobResponseHandler(reporting, res, jobTypes, user, { docId }); + return jobManagementPreRouting(reporting, res, docId, user, counters, async (doc) => { + const payload = await jobsQuery.getDocumentPayload(doc); + + if (!payload.contentType || !ALLOWED_JOB_CONTENT_TYPES.includes(payload.contentType)) { + return res.badRequest({ + body: `Unsupported content-type of ${payload.contentType} specified by job output`, + }); + } + + return res.custom({ + body: + typeof payload.content === 'string' ? Buffer.from(payload.content) : payload.content, + statusCode: payload.statusCode, + headers: { + ...payload.headers, + 'content-type': payload.contentType, + }, + }); + }); }) ); }; @@ -197,7 +200,7 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { }, }, authorizedUserPreRouting(reporting, async (user, context, req, res) => { - incrementApiUsageCounter(req.route.method, path, reporting.getUsageCounter()); + const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); // ensure the async dependencies are loaded if (!context.reporting) { @@ -205,11 +208,19 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { } const { docId } = req.params; - const { - management: { jobTypes = [] }, - } = await reporting.getLicenseInfo(); - return deleteJobResponseHandler(reporting, res, jobTypes, user, { docId }); + return jobManagementPreRouting(reporting, res, docId, user, counters, async (doc) => { + const docIndex = doc.index; + const stream = await getContentStream(reporting, { id: docId, index: docIndex }); + + /** @note Overwriting existing content with an empty buffer to remove all the chunks. */ + await promisify(stream.end.bind(stream, '', 'utf8'))(); + await jobsQuery.delete(docIndex, docId); + + return res.ok({ + body: { deleted: true }, + }); + }); }) ); }; diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts index 6266bb60056d8..3495212a31f00 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/setup_fleet_for_endpoint.ts @@ -95,6 +95,9 @@ export const installOrUpgradeEndpointFleetPackage = async ( body: { packages: ['endpoint'], }, + query: { + prerelease: true, + }, }) .catch(wrapErrorAndRejectPromise)) as AxiosResponse; diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts index 9938dd176574a..9511e96956979 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts @@ -215,10 +215,7 @@ describe('Custom query rules', () => { cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should( - 'have.text', - ruleFields.defaultIndexPatterns.join('') - ); + getDetails(INDEX_PATTERNS_DETAILS).should('have.text', 'auditbeat-*'); getDetails(CUSTOM_QUERY_DETAILS).should('have.text', ruleFields.ruleQuery); getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query'); getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts index 2c0507ca38157..6d8ad918b98c8 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/new_terms_rule.cy.ts @@ -7,7 +7,7 @@ import { formatMitreAttackDescription } from '../../helpers/rules'; import type { Mitre } from '../../objects/rule'; -import { getNewTermsRule, getIndexPatterns } from '../../objects/rule'; +import { getNewTermsRule } from '../../objects/rule'; import { ALERT_DATA_GRID } from '../../screens/alerts'; import { @@ -128,7 +128,7 @@ describe('New Terms rules', () => { cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join('')); + getDetails(INDEX_PATTERNS_DETAILS).should('have.text', 'auditbeat-*'); getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery); getDetails(RULE_TYPE_DETAILS).should('have.text', 'New Terms'); getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts index 68973fdc56941..c67b7b3fcc57b 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/override.cy.ts @@ -7,7 +7,7 @@ import { formatMitreAttackDescription } from '../../helpers/rules'; import type { Mitre, OverrideRule } from '../../objects/rule'; -import { getIndexPatterns, getNewOverrideRule, getSeveritiesOverride } from '../../objects/rule'; +import { getNewOverrideRule, getSeveritiesOverride } from '../../objects/rule'; import type { CompleteTimeline } from '../../objects/timeline'; import { NUMBER_OF_ALERTS, ALERT_GRID_CELL } from '../../screens/alerts'; @@ -146,7 +146,7 @@ describe('Detection rules, override', () => { cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join('')); + getDetails(INDEX_PATTERNS_DETAILS).should('have.text', 'auditbeat-*'); getDetails(CUSTOM_QUERY_DETAILS).should('have.text', this.rule.customQuery); getDetails(RULE_TYPE_DETAILS).should('have.text', 'Query'); getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts index 59efa1cced40c..2b6032caf7c41 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/threshold_rule.cy.ts @@ -7,7 +7,7 @@ import { formatMitreAttackDescription } from '../../helpers/rules'; import type { Mitre } from '../../objects/rule'; -import { getIndexPatterns, getNewThresholdRule } from '../../objects/rule'; +import { getNewThresholdRule } from '../../objects/rule'; import { ALERT_GRID_CELL, NUMBER_OF_ALERTS } from '../../screens/alerts'; @@ -124,7 +124,7 @@ describe('Detection rules, threshold', () => { cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join('')); + getDetails(INDEX_PATTERNS_DETAILS).should('have.text', 'auditbeat-*'); getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.customQuery); getDetails(RULE_TYPE_DETAILS).should('have.text', 'Threshold'); getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index 1d59f2ce83ce1..add4bdb9087e5 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -228,3 +228,8 @@ export const savedQueryByName = (savedQueryName: string) => export const APPLY_SELECTED_SAVED_QUERY_BUTTON = '[data-test-subj="saved-query-management-apply-changes-button"]'; + +export const RULE_INDICES = + '[data-test-subj="detectionEngineStepDefineRuleIndices"] [data-test-subj="comboBoxInput"]'; + +export const ALERTS_INDEX_BUTTON = 'span[title=".alerts-security.alerts-default"] button'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index fe3809f1d3cc7..3660f7c3d836d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -105,6 +105,8 @@ import { ACTIONS_THROTTLE_INPUT, CONTINUE_BUTTON, CREATE_WITHOUT_ENABLING_BTN, + RULE_INDICES, + ALERTS_INDEX_BUTTON, } from '../screens/create_new_rule'; import { INDEX_SELECTOR, @@ -239,6 +241,7 @@ export const importSavedQuery = (timelineId: string) => { cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click(); cy.get(TIMELINE(timelineId)).click(); cy.get(CUSTOM_QUERY_INPUT).should('not.be.empty'); + removeAlertsIndex(); }; export const fillRuleName = (ruleName: string = ruleFields.ruleName) => { @@ -344,6 +347,9 @@ const fillCustomQuery = (rule: CustomRule | OverrideRule) => { cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click(); cy.get(TIMELINE(rule.timeline.id)).click(); cy.get(CUSTOM_QUERY_INPUT).should('have.value', rule.customQuery); + if (rule.dataSource.type === 'indexPatterns') { + removeAlertsIndex(); + } } else { cy.get(CUSTOM_QUERY_INPUT) .first() @@ -351,6 +357,17 @@ const fillCustomQuery = (rule: CustomRule | OverrideRule) => { } }; +// called after import rule from saved timeline +// if alerts index is created, it is included in the timeline +// to be consistent in multiple test runs, remove it if it's there +export const removeAlertsIndex = () => { + cy.get(RULE_INDICES).then(($body) => { + if ($body.find(ALERTS_INDEX_BUTTON).length > 0) { + cy.get(ALERTS_INDEX_BUTTON).click(); + } + }); +}; + export const continueWithNextSection = () => { cy.get(CONTINUE_BUTTON).should('exist').click(); }; @@ -690,20 +707,3 @@ export const checkLoadQueryDynamically = () => { export const uncheckLoadQueryDynamically = () => { cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).click({ force: true }).should('not.be.checked'); }; - -export const defineSection = { importSavedQuery }; -export const aboutSection = { - fillRuleName, - fillDescription, - fillSeverity, - fillRiskScore, - fillRuleTags, - expandAdvancedSettings, - fillReferenceUrls, - fillFalsePositiveExamples, - fillThreat, - fillThreatTechnique, - fillThreatSubtechnique, - fillNote, -}; -export const scheduleSection = { fillFrom }; diff --git a/x-pack/plugins/security_solution/public/common/components/page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page/index.tsx index 1cab436f6d658..44884c0b15198 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/index.tsx @@ -35,6 +35,9 @@ export const FULL_SCREEN_CONTENT_OVERRIDES_CSS_STYLESHEET = () => css` } `; +/** The `z-index` for EuiPopover Panels that are displayed from inside of Timeline page */ +export const TIMELINE_EUI_POPOVER_PANEL_ZINDEX = 9900; + /** * Stylesheet with Eui class overrides in order to address display issues caused when * the Timeline overlay is opened. These are normally adjustments to ensure that the @@ -43,7 +46,7 @@ export const FULL_SCREEN_CONTENT_OVERRIDES_CSS_STYLESHEET = () => css` */ export const TIMELINE_OVERRIDES_CSS_STYLESHEET = () => css` .euiPopover__panel[data-popover-open] { - z-index: 9900 !important; + z-index: ${TIMELINE_EUI_POPOVER_PANEL_ZINDEX} !important; min-width: 24px; } .euiPopover__panel[data-popover-open].sourcererPopoverPanel { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/translations.ts index 124c3c3805217..eb435f4ed54a7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/translations.ts @@ -28,14 +28,6 @@ export const RULE_PREVIEW_TITLE = i18n.translate( } ); -export const RULE_PREVIEW_DESCRIPTION = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.rulePreviewDescription', - { - defaultMessage: - 'Rule preview reflects the current configuration of your rule settings and exceptions, click refresh icon to see the updated preview.', - } -); - export const CANCEL_BUTTON_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.cancelButtonLabel', { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx index 793e138cebfa4..88073254bfbbe 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.test.tsx @@ -6,7 +6,6 @@ */ import React from 'react'; -import { mount } from 'enzyme'; import { QueryBarDefineRule } from '.'; import { @@ -16,7 +15,11 @@ import { } from '../../../../common/mock'; import { useGetAllTimeline, getAllTimeline } from '../../../../timelines/containers/all'; import { mockHistory, Router } from '../../../../common/mock/router'; +import { render, act, fireEvent } from '@testing-library/react'; +import { resolveTimeline } from '../../../../timelines/containers/api'; +import { mockTimeline } from '../../../../../server/lib/timeline/__mocks__/create_timelines'; +jest.mock('../../../../timelines/containers/api'); jest.mock('../../../../common/lib/kibana', () => { const actual = jest.requireActual('../../../../common/lib/kibana'); return { @@ -48,6 +51,7 @@ jest.mock('../../../../timelines/containers/all', () => { describe('QueryBarDefineRule', () => { beforeEach(() => { + jest.clearAllMocks(); (useGetAllTimeline as unknown as jest.Mock).mockReturnValue({ fetchAllTimeline: jest.fn(), timelines: getAllTimeline('', mockOpenTimelineQueryResults.timeline ?? []), @@ -55,61 +59,87 @@ describe('QueryBarDefineRule', () => { totalCount: mockOpenTimelineQueryResults.totalCount, refetch: jest.fn(), }); + (resolveTimeline as jest.Mock).mockResolvedValue({ + data: { + timeline: { mockTimeline }, + }, + }); }); it('renders correctly', () => { - const Component = () => { - const field = useFormFieldMock(); + const field = useFormFieldMock(); - return ( - - ); - }; - const wrapper = mount( + const { getByTestId } = render( - + ); - expect(wrapper.find('[data-test-subj="query-bar-define-rule"]').exists()).toBeTruthy(); + expect(getByTestId('query-bar-define-rule')).toBeInTheDocument(); }); - it('renders import query from saved timeline modal actions hidden correctly', () => { - const Component = () => { + it('renders import query from saved timeline modal actions hidden correctly', async () => { + await act(async () => { const field = useFormFieldMock(); - return ( - + const { queryByTestId } = render( + + + + + ); - }; - const wrapper = mount( + + expect(queryByTestId('open-duplicate')).not.toBeInTheDocument(); + expect(queryByTestId('create-from-template')).not.toBeInTheDocument(); + }); + }); + + it('calls onOpenTimeline correctly', async () => { + const field = useFormFieldMock(); + const onOpenTimeline = jest.fn(); + + const { getByTestId } = render( - + ); + getByTestId('open-timeline-modal').click(); - expect(wrapper.find('[data-test-subj="open-duplicate"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="create-from-template"]').exists()).toBeFalsy(); + await act(async () => { + fireEvent.click(getByTestId('title-10849df0-7b44-11e9-a608-ab3d811609')); + }); + expect(onOpenTimeline).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx index ec198514f7e82..c68194187e4b7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_bar/index.tsx @@ -17,9 +17,6 @@ import type { BrowserFields } from '../../../../common/containers/source'; import { OpenTimelineModal } from '../../../../timelines/components/open_timeline/open_timeline_modal'; import type { ActionTimelineToShow } from '../../../../timelines/components/open_timeline/types'; import { QueryBar } from '../../../../common/components/query_bar'; -import { buildGlobalQuery } from '../../../../timelines/components/timeline/helpers'; -import { getDataProviderFilter } from '../../../../timelines/components/timeline/query_bar'; -import { convertKueryToElasticSearchQuery } from '../../../../common/lib/kuery'; import { useKibana } from '../../../../common/lib/kibana'; import type { TimelineModel } from '../../../../timelines/store/timeline/model'; import { useSavedQueryServices } from '../../../../common/utils/saved_query_services'; @@ -54,6 +51,7 @@ export interface QueryBarDefineRuleProps { */ onSavedQueryError?: () => void; defaultSavedQuery?: SavedQuery | undefined; + onOpenTimeline?: (timeline: TimelineModel) => void; } const actionTimelineToHide: ActionTimelineToShow[] = ['duplicate', 'createFrom']; @@ -88,6 +86,7 @@ export const QueryBarDefineRule = ({ onValidityChange, isDisabled, resetToSavedQuery, + onOpenTimeline, onSavedQueryError, }: QueryBarDefineRuleProps) => { const { value: fieldValue, setValue: setFieldValue } = field as FieldHook; @@ -234,31 +233,12 @@ export const QueryBarDefineRule = ({ onCloseTimelineSearch(); }, [onCloseTimelineSearch]); - const onOpenTimeline = useCallback( + const onOpenTimelineCb = useCallback( (timeline: TimelineModel) => { setLoadingTimeline(false); - const newQuery = { - query: timeline.kqlQuery.filterQuery?.kuery?.expression ?? '', - language: timeline.kqlQuery.filterQuery?.kuery?.kind ?? 'kuery', - }; - const dataProvidersDsl = - timeline.dataProviders != null && timeline.dataProviders.length > 0 - ? convertKueryToElasticSearchQuery( - buildGlobalQuery(timeline.dataProviders, browserFields), - indexPattern - ) - : ''; - const newFilters = timeline.filters ?? []; - setFieldValue({ - filters: - dataProvidersDsl !== '' - ? [...newFilters, getDataProviderFilter(dataProvidersDsl)] - : newFilters, - query: newQuery, - saved_id: null, - }); + onOpenTimeline?.(timeline); }, - [browserFields, indexPattern, setFieldValue] + [onOpenTimeline] ); const onMutation = () => { @@ -324,7 +304,7 @@ export const QueryBarDefineRule = ({ hideActions={actionTimelineToHide} modalTitle={i18n.IMPORT_TIMELINE_MODAL} onClose={onCloseTimelineModal} - onOpen={onOpenTimeline} + onOpen={onOpenTimelineCb} /> ) : null} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.test.tsx index 4e41ac0f996ea..524cd562dd1a7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.test.tsx @@ -10,6 +10,7 @@ import { shallow } from 'enzyme'; import { StepDefineRule, aggregatableFields } from '.'; import { stepDefineDefaultValue } from '../../../pages/detection_engine/rules/utils'; +import { mockBrowserFields } from '../../../../common/containers/source/mock'; jest.mock('../../../../common/lib/kibana'); jest.mock('../../../../common/hooks/use_selector', () => { @@ -19,6 +20,21 @@ jest.mock('../../../../common/hooks/use_selector', () => { useDeepEqualSelector: () => ({ kibanaDataViews: [{ id: 'world' }], sourcererScope: 'my-selected-dataview-id', + selectedDataView: { + id: 'security-solution', + browserFields: mockBrowserFields, + patternList: [], + }, + }), + }; +}); +jest.mock('../../../../common/components/link_to', () => { + const originalModule = jest.requireActual('../../../../common/components/link_to'); + return { + ...originalModule, + getTimelineUrl: jest.fn(), + useFormatUrl: jest.fn().mockReturnValue({ + formatUrl: jest.fn().mockImplementation((path: string) => path), }), }; }); @@ -36,6 +52,15 @@ jest.mock('react-router-dom', () => { return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '/alerts' }) }; }); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + + return { + ...original, + useDispatch: jest.fn(), + }; +}); + test('aggregatableFields', function () { expect( aggregatableFields([ 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 45db51db150f6..554eb0df47774 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 @@ -28,6 +28,7 @@ import usePrevious from 'react-use/lib/usePrevious'; import type { SavedQuery } from '@kbn/data-plugin/public'; import type { DataViewBase } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useRuleFromTimeline } from '../../../containers/detection_engine/rules/use_rule_from_timeline'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license'; @@ -146,7 +147,7 @@ const StepDefineRuleComponent: FC = ({ schema, }); - const { getFields, getFormData, reset, validate } = form; + const { getFields, getFormData, reset, setFieldValue, validate } = form; const [formData] = useFormData({ form, watch: [ @@ -178,6 +179,17 @@ const StepDefineRuleComponent: FC = ({ } }, }); + + const handleSetRuleFromTimeline = useCallback( + ({ index: timelineIndex, queryBar: timelineQueryBar }) => { + setFieldValue('index', timelineIndex); + setFieldValue('queryBar', timelineQueryBar); + }, + [setFieldValue] + ); + const { onOpenTimeline, loading: timelineQueryLoading } = + useRuleFromTimeline(handleSetRuleFromTimeline); + const { index: formIndex, ruleType: formRuleType, @@ -187,18 +199,19 @@ const StepDefineRuleComponent: FC = ({ threatMapping: formThreatMapping, machineLearningJobId: formMachineLearningJobId, dataSourceType: formDataSourceType, - newTermsFields: formNewTermsFields, + newTermsFields, shouldLoadQueryDynamically: formShouldLoadQueryDynamically, } = formData; const [isQueryBarValid, setIsQueryBarValid] = useState(false); const [isThreatQueryBarValid, setIsThreatQueryBarValid] = useState(false); const index = formIndex || initialState.index; - const dataView = formDataViewId || initialState.dataViewId; + const dataViewId = formDataViewId || initialState.dataViewId; const threatIndex = formThreatIndex || initialState.threatIndex; const ruleType = formRuleType || initialState.ruleType; const dataSourceType = formDataSourceType || initialState.dataSourceType; const machineLearningJobId = formMachineLearningJobId ?? initialState.machineLearningJobId; + const queryBar = formQuery ?? initialState.queryBar; const [isPreviewValid, setIsPreviewValid] = useState(false); useEffect(() => { @@ -212,23 +225,24 @@ const StepDefineRuleComponent: FC = ({ isQueryBarValid, isThreatQueryBarValid, index, - dataViewId: formDataViewId, + dataViewId, dataSourceType, threatIndex, threatMapping: formThreatMapping, machineLearningJobId, - queryBar: formQuery ?? initialState.queryBar, - newTermsFields: formNewTermsFields, + queryBar, + newTermsFields, }); setIsPreviewValid(!isDisabled); }, [ dataSourceType, formDataViewId, - formNewTermsFields, + newTermsFields, formQuery, + dataViewId, formThreatMapping, index, - initialState.queryBar, + queryBar, isQueryBarValid, isThreatQueryBarValid, machineLearningJobId, @@ -263,8 +277,8 @@ const StepDefineRuleComponent: FC = ({ if (dataSourceType === DataSourceType.DataView) { const fetchDataView = async () => { - if (dataView != null) { - const dv = await data.dataViews.get(dataView); + if (dataViewId != null) { + const dv = await data.dataViews.get(dataViewId); setDataViewTitle(dv.title); setIndexPattern(dv); } @@ -272,7 +286,7 @@ const StepDefineRuleComponent: FC = ({ fetchDataView(); } - }, [dataSourceType, isIndexPatternLoading, data, dataView, initIndexPattern]); + }, [dataSourceType, isIndexPatternLoading, data, dataViewId, initIndexPattern]); // Callback for when user toggles between Data Views and Index Patterns const onChangeDataSource = useCallback( @@ -334,8 +348,8 @@ const StepDefineRuleComponent: FC = ({ * * from '*:*' back to '' if the type is switched back from "threat_match" to another one */ useEffect(() => { - const { queryBar } = getFields(); - if (queryBar == null) { + const { queryBar: currentQuery } = getFields(); + if (currentQuery == null) { return; } @@ -351,15 +365,15 @@ const StepDefineRuleComponent: FC = ({ // NOTE: It's important to do a deep object comparison by value. // Don't do it by reference because the forms lib can change it internally. - // 2. We call queryBar.reset() in both cases to not trigger validation errors + // 2. We call currentQuery.reset() in both cases to not trigger validation errors // as the user has not entered data into those areas yet. // If the user switched rule type to "threat_match" from any other one, // but hasn't changed the custom query used for normal rules (''), // we reset the custom query to the default used for "threat_match" rules ('*:*'). if (isThreatMatchRule(ruleType) && !isThreatMatchRule(previousRuleType)) { - if (isEqual(queryBar.value, defaultCustomQuery.forNormalRules)) { - queryBar.reset({ + if (isEqual(currentQuery.value, defaultCustomQuery.forNormalRules)) { + currentQuery.reset({ defaultValue: defaultCustomQuery.forThreatMatchRules, }); return; @@ -370,8 +384,8 @@ const StepDefineRuleComponent: FC = ({ // but hasn't changed the custom query used for "threat_match" rules ('*:*'), // we reset the custom query to another default value (''). if (!isThreatMatchRule(ruleType) && isThreatMatchRule(previousRuleType)) { - if (isEqual(queryBar.value, defaultCustomQuery.forThreatMatchRules)) { - queryBar.reset({ + if (isEqual(currentQuery.value, defaultCustomQuery.forThreatMatchRules)) { + currentQuery.reset({ defaultValue: defaultCustomQuery.forNormalRules, }); } @@ -451,11 +465,6 @@ const StepDefineRuleComponent: FC = ({ ), [aggFields] ); - const SourcererFlex = styled(EuiFlexItem)` - align-items: flex-end; - `; - - SourcererFlex.displayName = 'SourcererFlex'; const ThreatMatchInputChildren = useCallback( ({ threatMapping }) => ( @@ -585,6 +594,8 @@ const StepDefineRuleComponent: FC = ({ euiFieldProps: { fullWidth: true, placeholder: '', + isDisabled: timelineQueryLoading, + isLoading: timelineQueryLoading, }, }} /> @@ -594,6 +605,7 @@ const StepDefineRuleComponent: FC = ({ ); }, [ + timelineQueryLoading, dataSourceType, onChangeDataSource, dataViewIndexPatternToggleButtonOptions, @@ -626,30 +638,33 @@ const StepDefineRuleComponent: FC = ({ browserFields, idAria: 'detectionEngineStepDefineRuleQueryBar', indexPattern, - isDisabled: isLoading || formShouldLoadQueryDynamically, + isDisabled: isLoading || formShouldLoadQueryDynamically || timelineQueryLoading, resetToSavedQuery: formShouldLoadQueryDynamically, - isLoading: isIndexPatternLoading, + isLoading: isIndexPatternLoading || timelineQueryLoading, dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', openTimelineSearch, onValidityChange: setIsQueryBarValid, onCloseTimelineSearch: handleCloseTimelineSearch, onSavedQueryError: handleSavedQueryError, defaultSavedQuery, + onOpenTimeline, } as QueryBarDefineRuleProps } /> ), [ - browserFields, - handleCloseTimelineSearch, handleOpenTimelineSearch, + formShouldLoadQueryDynamically, + browserFields, indexPattern, - isIndexPatternLoading, isLoading, + timelineQueryLoading, + isIndexPatternLoading, openTimelineSearch, - formShouldLoadQueryDynamically, + handleCloseTimelineSearch, handleSavedQueryError, defaultSavedQuery, + onOpenTimeline, ] ); const onOptionsChange = useCallback((field: FieldsEqlOptions, value: string | undefined) => { @@ -761,7 +776,7 @@ const StepDefineRuleComponent: FC = ({ = ({ 'data-test-subj': 'detectionEngineStepDefineRuleShouldLoadQueryDynamically', euiFieldProps: { disabled: isLoading, - label: formQuery?.title - ? i18n.getSavedQueryCheckboxLabel(formQuery.title) + label: queryBar?.title + ? i18n.getSavedQueryCheckboxLabel(queryBar.title) : undefined, }, }} diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.test.ts new file mode 100644 index 0000000000000..1ed56a518662a --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.test.ts @@ -0,0 +1,323 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { useRuleFromTimeline } from './use_rule_from_timeline'; +import { useGetInitialUrlParamValue } from '../../../../common/utils/global_query_string/helpers'; +import { resolveTimeline } from '../../../../timelines/containers/api'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; +import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { mockTimeline } from '../../../../../server/lib/timeline/__mocks__/create_timelines'; +import type { TimelineModel } from '../../../..'; + +jest.mock('../../../../common/utils/global_query_string/helpers'); +jest.mock('../../../../timelines/containers/api'); +jest.mock('../../../../common/hooks/use_app_toasts'); +jest.mock('../../../../common/containers/sourcerer'); +jest.mock('../../../../common/components/link_to', () => { + const originalModule = jest.requireActual('../../../../common/components/link_to'); + return { + ...originalModule, + getTimelineUrl: jest.fn(), + useFormatUrl: jest.fn().mockReturnValue({ + formatUrl: jest.fn().mockImplementation((path: string) => path), + }), + }; +}); + +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + return { + ...original, + useDispatch: () => mockDispatch, + }; +}); + +const timelineId = 'eb2781c0-1df5-11eb-8589-2f13958b79f7'; + +const selectedTimeline = { + data: { + timeline: { + ...mockTimeline, + id: timelineId, + savedObjectId: timelineId, + indexNames: ['awesome-*'], + dataViewId: 'custom-data-view-id', + kqlQuery: { + filterQuery: { + serializedQuery: + '{"bool":{"filter":[{"bool":{"should":[{"exists":{"field":"host.name"}}],"minimum_should_match":1}},{"bool":{"should":[{"exists":{"field":"user.name"}}],"minimum_should_match":1}}]}}', + kuery: { + expression: 'host.name:* AND user.name:*', + kind: 'kuery', + }, + }, + }, + dataProviders: [ + { + excluded: false, + and: [], + kqlQuery: '', + name: 'Stephs-MBP.lan', + queryMatch: { + field: 'host.name', + value: 'Stephs-MBP.lan', + operator: ':', + }, + id: 'draggable-badge-default-draggable-process_stopped-timeline-1-NH9UwoMB2HTqQ3G4wUFM-host_name-Stephs-MBP_lan', + enabled: true, + }, + { + excluded: false, + and: [], + kqlQuery: '', + name: '--lang=en-US', + queryMatch: { + field: 'process.args', + value: '--lang=en-US', + operator: ':', + }, + id: 'draggable-badge-default-draggable-process_started-timeline-1-args-5---lang=en-US-MH9TwoMB2HTqQ3G4_UH--process_args---lang=en-US', + enabled: true, + }, + ], + }, + }, +}; + +describe('useRuleFromTimeline', () => { + let appToastsMock: jest.Mocked>; + const setRuleQuery = jest.fn(); + beforeEach(() => { + jest.clearAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + (useGetInitialUrlParamValue as jest.Mock).mockReturnValue(() => ({ + decodedParam: timelineId, + })); + (resolveTimeline as jest.Mock).mockResolvedValue(selectedTimeline); + }); + + describe('initial data view === rule from timeline data view', () => { + beforeEach(() => { + (useSourcererDataView as jest.Mock).mockReturnValue({ + ...mockSourcererScope, + dataViewId: 'custom-data-view-id', + selectedPatterns: ['awesome-*'], + }); + }); + + it('does not reset timeline sourcerer if it originally had same data view as the timeline used in the rule', async () => { + const { result, waitForNextUpdate } = renderHook(() => useRuleFromTimeline(setRuleQuery)); + expect(result.current.loading).toEqual(true); + await waitForNextUpdate(); + expect(setRuleQuery).toHaveBeenCalled(); + expect(mockDispatch).toHaveBeenCalledTimes(2); + }); + }); + + describe('initial data view !== rule from timeline data view', () => { + beforeEach(() => { + (useSourcererDataView as jest.Mock) + .mockReturnValueOnce({ + ...mockSourcererScope, + dataViewId: 'security-solution', + selectedPatterns: ['auditbeat-*'], + }) + .mockReturnValue({ + ...mockSourcererScope, + dataViewId: 'custom-data-view-id', + selectedPatterns: ['awesome-*'], + }); + }); + it('if no timeline id in URL, loading: false and query not set', async () => { + (useGetInitialUrlParamValue as jest.Mock).mockReturnValue(() => ({ + decodedParam: undefined, + })); + const { result } = renderHook(() => useRuleFromTimeline(setRuleQuery)); + + expect(result.current.loading).toEqual(false); + expect(setRuleQuery).not.toHaveBeenCalled(); + }); + + it('if timeline id in URL, set active timeline data view to from timeline data view', async () => { + const { result, waitForNextUpdate } = renderHook(() => useRuleFromTimeline(setRuleQuery)); + expect(result.current.loading).toEqual(true); + await waitForNextUpdate(); + expect(setRuleQuery).toHaveBeenCalled(); + + expect(mockDispatch).toHaveBeenCalledTimes(4); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { + type: 'x-pack/security_solution/local/timeline/UPDATE_LOADING', + payload: { + id: 'timeline-1', + isLoading: true, + }, + }); + + expect(mockDispatch).toHaveBeenNthCalledWith(2, { + type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', + payload: { + id: 'timeline', + selectedDataViewId: selectedTimeline.data.timeline.dataViewId, + selectedPatterns: selectedTimeline.data.timeline.indexNames, + }, + }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { + type: 'x-pack/security_solution/local/timeline/UPDATE_LOADING', + payload: { + id: 'timeline-1', + isLoading: false, + }, + }); + }); + + it('when from timeline data view id === selected data view id and browser fields is not empty, set rule data to match from timeline query', async () => { + const { result, waitForNextUpdate } = renderHook(() => useRuleFromTimeline(setRuleQuery)); + expect(result.current.loading).toEqual(true); + await waitForNextUpdate(); + expect(result.current.loading).toEqual(false); + expect(setRuleQuery).toHaveBeenCalledWith({ + index: ['awesome-*'], + queryBar: { + filters: [ + { + bool: { + should: [ + { + bool: { + should: [{ match_phrase: { 'host.name': 'Stephs-MBP.lan' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match_phrase: { 'process.args': '--lang=en-US' } }], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + meta: { + alias: 'timeline-filter-drop-area', + controlledBy: 'timeline-filter-drop-area', + negate: false, + disabled: false, + type: 'custom', + key: 'bool', + value: + '{"bool":{"should":[{"bool":{"should":[{"match_phrase":{"host.name":"Stephs-MBP.lan"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"process.args":"--lang=en-US"}}],"minimum_should_match":1}}],"minimum_should_match":1}}', + }, + $state: { store: 'appState' }, + }, + ], + query: { query: 'host.name:* AND user.name:*', language: 'kuery' }, + saved_id: null, + }, + }); + }); + + it('Sets rule from timeline query via callback', async () => { + (useGetInitialUrlParamValue as jest.Mock).mockReturnValue(() => ({ + decodedParam: undefined, + })); + const { result } = renderHook(() => useRuleFromTimeline(setRuleQuery)); + expect(result.current.loading).toEqual(false); + await act(async () => { + result.current.onOpenTimeline(selectedTimeline.data.timeline as unknown as TimelineModel); + }); + + // not loading anything as an external call to onOpenTimeline provides the timeline + expect(result.current.loading).toEqual(false); + expect(setRuleQuery).toHaveBeenCalledWith({ + index: ['awesome-*'], + queryBar: { + filters: [ + { + bool: { + should: [ + { + bool: { + should: [{ match_phrase: { 'host.name': 'Stephs-MBP.lan' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match_phrase: { 'process.args': '--lang=en-US' } }], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + meta: { + alias: 'timeline-filter-drop-area', + controlledBy: 'timeline-filter-drop-area', + negate: false, + disabled: false, + type: 'custom', + key: 'bool', + value: + '{"bool":{"should":[{"bool":{"should":[{"match_phrase":{"host.name":"Stephs-MBP.lan"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"process.args":"--lang=en-US"}}],"minimum_should_match":1}}],"minimum_should_match":1}}', + }, + $state: { store: 'appState' }, + }, + ], + query: { query: 'host.name:* AND user.name:*', language: 'kuery' }, + saved_id: null, + }, + }); + }); + + it('Handles error when query is malformed', async () => { + (useGetInitialUrlParamValue as jest.Mock).mockReturnValue(() => ({ + decodedParam: undefined, + })); + const { result } = renderHook(() => useRuleFromTimeline(setRuleQuery)); + expect(result.current.loading).toEqual(false); + const tl = { + ...selectedTimeline.data.timeline, + dataProviders: [ + { + property: 'bad', + }, + ], + }; + await act(async () => { + result.current.onOpenTimeline(tl as unknown as TimelineModel); + }); + + // not loading anything as an external call to onOpenTimeline provides the timeline + expect(result.current.loading).toEqual(false); + expect(setRuleQuery).not.toHaveBeenCalled(); + expect(appToastsMock.addError).toHaveBeenCalled(); + expect(appToastsMock.addError.mock.calls[0][0]).toEqual( + TypeError('dataProvider.and is not iterable') + ); + }); + + it('resets timeline sourcerer if it originally had different data view from the timeline used in the rule', async () => { + const { waitForNextUpdate } = renderHook(() => useRuleFromTimeline(setRuleQuery)); + await waitForNextUpdate(); + expect(setRuleQuery).toHaveBeenCalled(); + expect(mockDispatch).toHaveBeenNthCalledWith(4, { + type: 'x-pack/security_solution/local/sourcerer/SET_SELECTED_DATA_VIEW', + payload: { + id: 'timeline', + selectedDataViewId: 'security-solution', + selectedPatterns: ['auditbeat-*'], + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx new file mode 100644 index 0000000000000..e77fe4e92b7a3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_from_timeline.tsx @@ -0,0 +1,220 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash/fp'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import { convertKueryToElasticSearchQuery } from '../../../../common/lib/kuery'; +import { updateIsLoading } from '../../../../timelines/store/timeline/actions'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; +import type { TimelineModel } from '../../../..'; +import type { FieldValueQueryBar } from '../../../components/rules/query_bar'; +import { sourcererActions } from '../../../../common/store/sourcerer'; +import { + dispatchUpdateTimeline, + queryTimelineById, +} from '../../../../timelines/components/open_timeline/helpers'; +import { useGetInitialUrlParamValue } from '../../../../common/utils/global_query_string/helpers'; +import { buildGlobalQuery } from '../../../../timelines/components/timeline/helpers'; +import { getDataProviderFilter } from '../../../../timelines/components/timeline/query_bar'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; + +export const RULE_FROM_TIMELINE_URL_PARAM = 'createRuleFromTimeline'; + +export interface RuleFromTimeline { + loading: boolean; + onOpenTimeline: (timeline: TimelineModel) => void; +} + +export const initialState = { + index: [], + queryBar: { + query: { query: '', language: 'kuery' }, + filters: [], + saved_id: null, + }, +}; + +type SetRuleQuery = ({ + index, + queryBar, +}: { + index: string[]; + queryBar: FieldValueQueryBar; +}) => void; + +/** + * When returned property updated === true, + * the index and queryBar properties have been updated from timeline data + * queried either from id in the url param or by passing a timeline to returned callback onOpenTimeline + */ +export const useRuleFromTimeline = (setRuleQuery: SetRuleQuery): RuleFromTimeline => { + const dispatch = useDispatch(); + const { addError } = useAppToasts(); + const { browserFields, dataViewId, selectedPatterns } = useSourcererDataView( + SourcererScopeName.timeline + ); + + // selectedTimeline = timeline to set rule from + const [selectedTimeline, setRuleFromTimeline] = useState(null); + + const [loading, setLoading] = useState(false); + + const onOpenTimeline = useCallback( + (timeline: TimelineModel) => { + // will already be true if timeline set from url + setLoading(true); + setRuleFromTimeline(timeline); + + if (timeline.dataViewId !== dataViewId && !isEmpty(timeline.indexNames)) { + // let sourcerer manage the selected browser fields by setting timeline scope to the selected timeline data view + // sourcerer handles the logic of if the fields have been fetched or need to be fetched + dispatch( + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.timeline, + selectedDataViewId: timeline.dataViewId, + selectedPatterns: timeline.indexNames, + }) + ); + } + }, + [dataViewId, dispatch] + ); + + // start browser field management + const [originalDataView] = useState({ dataViewId, selectedPatterns }); + + const selectedDataViewBrowserFields = useMemo( + () => + selectedTimeline == null || + isEmpty(browserFields) || + (selectedTimeline.dataViewId !== null && + dataViewId !== null && + dataViewId !== selectedTimeline.dataViewId) + ? null + : browserFields, + [browserFields, dataViewId, selectedTimeline] + ); + // end browser field management + + // start set rule + const handleSetRuleFromTimeline = useCallback(() => { + if (selectedTimeline == null || selectedDataViewBrowserFields == null) return; + + const newQuery = { + query: selectedTimeline.kqlQuery.filterQuery?.kuery?.expression ?? '', + language: selectedTimeline.kqlQuery.filterQuery?.kuery?.kind ?? 'kuery', + }; + const newFilters = selectedTimeline.filters ?? []; + try { + const dataProvidersDsl = + selectedTimeline.dataProviders != null && selectedTimeline.dataProviders.length > 0 + ? convertKueryToElasticSearchQuery( + buildGlobalQuery(selectedTimeline.dataProviders, selectedDataViewBrowserFields), + { fields: [], title: selectedPatterns.join(',') } + ) + : ''; + + setLoading(false); + + setRuleQuery({ + index: selectedPatterns, + queryBar: { + filters: + dataProvidersDsl !== '' + ? [...newFilters, getDataProviderFilter(dataProvidersDsl)] + : newFilters, + query: newQuery, + saved_id: null, + }, + }); + } catch (error) { + setLoading(false); + addError(error, { + toastMessage: i18n.translate('xpack.securitySolution.ruleFromTimeline.error.toastMessage', { + defaultMessage: 'Failed to create rule from timeline with id: {id}', + values: { + id: selectedTimeline.id, + }, + }), + title: i18n.translate('xpack.securitySolution.ruleFromTimeline.error.title', { + defaultMessage: 'Failed to import rule from timeline', + }), + }); + } + + // reset timeline data view once complete + if (originalDataView.dataViewId !== dataViewId) { + dispatch( + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.timeline, + selectedDataViewId: originalDataView.dataViewId, + selectedPatterns: originalDataView.selectedPatterns, + }) + ); + } + }, [ + addError, + dataViewId, + dispatch, + originalDataView.dataViewId, + originalDataView.selectedPatterns, + selectedDataViewBrowserFields, + selectedPatterns, + selectedTimeline, + setRuleQuery, + ]); + + useEffect(() => { + // ensure browser fields are correct before updating the rule + if (selectedDataViewBrowserFields != null) { + handleSetRuleFromTimeline(); + } + }, [handleSetRuleFromTimeline, selectedDataViewBrowserFields]); + // end set rule + + // start handle set rule from timeline id + const getInitialUrlParamValue = useGetInitialUrlParamValue(RULE_FROM_TIMELINE_URL_PARAM); + const { decodedParam: timelineIdFromUrl } = useMemo(getInitialUrlParamValue, [ + getInitialUrlParamValue, + ]); + + const getTimelineById = useCallback( + (timelineId: string) => { + if (selectedTimeline == null || timelineId !== selectedTimeline.id) { + queryTimelineById({ + timelineId, + onOpenTimeline, + updateIsLoading: ({ + id: currentTimelineId, + isLoading, + }: { + id: string; + isLoading: boolean; + }) => dispatch(updateIsLoading({ id: currentTimelineId, isLoading })), + updateTimeline: dispatchUpdateTimeline(dispatch), + }); + } + }, + [dispatch, onOpenTimeline, selectedTimeline] + ); + + const [urlStateInitialized, setUrlStateInitialized] = useState(false); + + useEffect(() => { + if (timelineIdFromUrl != null && !urlStateInitialized) { + setUrlStateInitialized(true); + getTimelineById(timelineIdFromUrl); + setLoading(true); + } + }, [getTimelineById, timelineIdFromUrl, urlStateInitialized]); + // end handle set rule from timeline id + + return { loading, onOpenTimeline }; +}; diff --git a/x-pack/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx index 0858ee37c23fd..730d12f647ca0 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx @@ -55,6 +55,7 @@ export const ImportExceptionListFlyout = React.memo( const [file, setFile] = useState(null); const [overwrite, setOverwrite] = useState(false); const [asNewList, setAsNewList] = useState(false); + const [alreadyExistingItem, setAlreadyExistingItem] = useState(false); const resetForm = useCallback(() => { if (filePickerRef.current?.fileInput) { @@ -97,7 +98,8 @@ export const ImportExceptionListFlyout = React.memo( // eslint-disable-next-line react-hooks/exhaustive-deps [resetForm, addSuccess, handleRefresh] ); - const handleImportError = useCallback( + + const handleImportErrors = useCallback( (errors: BulkErrorSchema[]) => { errors.forEach((error) => { if (!error.error.message.includes('AbortError')) { @@ -107,23 +109,38 @@ export const ImportExceptionListFlyout = React.memo( }, [addError] ); - const [alreadyExistingItem, setAlreadyExistingItem] = useState(false); useEffect(() => { if (!importExceptionListState.loading) { if (importExceptionListState?.result?.success) { handleImportSuccess(importExceptionListState?.result); - } else if (importExceptionListState?.result?.errors) { - handleImportError(importExceptionListState?.result?.errors); + } else { + const errorsToDisplay: BulkErrorSchema[] = []; + // @ts-expect-error + if (importExceptionListState?.error?.body) { + errorsToDisplay.push({ + // @ts-expect-error + error: { ...importExceptionListState?.error?.body }, + }); + } + if (importExceptionListState?.result?.errors) { + importExceptionListState?.result?.errors.forEach((err) => { + if (err.error.message.includes('already exists')) { + setAlreadyExistingItem(true); + } + errorsToDisplay.push(err); + }); + } + handleImportErrors(errorsToDisplay); } } }, [ - handleImportError, + handleImportErrors, handleImportSuccess, - importExceptionListState.error, + importExceptionListState?.error, importExceptionListState.loading, - importExceptionListState.result, - setAlreadyExistingItem, + importExceptionListState?.result, + importExceptionListState?.result?.errors, ]); const handleFileChange = useCallback((files: FileList | null) => { setFile(files?.item(0) ?? null); diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/command_input.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/command_input.tsx index ef29cec010b21..0310b0251d5d0 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/command_input.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/command_input.tsx @@ -8,9 +8,11 @@ import type { MouseEventHandler } from 'react'; import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { CommonProps } from '@elastic/eui'; -import { EuiFlexGroup, EuiFlexItem, useResizeObserver, EuiButtonIcon } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiResizeObserver } from '@elastic/eui'; import styled from 'styled-components'; import classNames from 'classnames'; +import type { EuiResizeObserverProps } from '@elastic/eui/src/components/observer/resize_observer/resize_observer'; +import { useWithInputShowPopover } from '../../hooks/state_selectors/use_with_input_show_popover'; import { EnteredInput } from './lib/entered_input'; import type { InputCaptureProps } from './components/input_capture'; import { InputCapture } from './components/input_capture'; @@ -53,6 +55,11 @@ const CommandInputContainer = styled.div` background-color: ${({ theme }) => theme.eui.euiTextSubduedColor}; } + &.withPopover { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + &.hasFocus { .cursor { background-color: ${({ theme: { eui } }) => eui.euiTextColor}; @@ -86,41 +93,40 @@ export const CommandInput = memo(({ prompt = '', focusRef, .. const visibleState = useWithInputVisibleState(); const [isKeyInputBeingCaptured, setIsKeyInputBeingCaptured] = useState(false); const getTestId = useTestIdGenerator(useDataTestSubj()); + const isPopoverOpen = !!useWithInputShowPopover(); const [commandToExecute, setCommandToExecute] = useState(''); + const [popoverWidth, setPopoverWidth] = useState('94vw'); - const containerRef = useRef(null); - const dimensions = useResizeObserver(containerRef.current); const _focusRef: InputCaptureProps['focusRef'] = useRef(null); - const keyCaptureFocusRef = focusRef || _focusRef; - const popoverWidth = useMemo(() => { - return dimensions.width ? `${dimensions.width}px` : '92vw'; - }, [dimensions.width]); - const inputContainerClassname = useMemo(() => { return classNames({ cmdInput: true, hasFocus: isKeyInputBeingCaptured, error: visibleState === 'error', + withPopover: isPopoverOpen, }); - }, [isKeyInputBeingCaptured, visibleState]); + }, [isKeyInputBeingCaptured, isPopoverOpen, visibleState]); const disableArrowButton = useMemo(() => fullTextEntered.trim().length === 0, [fullTextEntered]); - const handleSubmitButton = useCallback( - (ev) => { - setCommandToExecute(textEntered + rightOfCursor.text); - dispatch({ - type: 'updateInputTextEnteredState', - payload: { - textEntered: '', - rightOfCursor: undefined, - }, - }); - }, - [dispatch, textEntered, rightOfCursor.text] - ); + const handleOnResize = useCallback(({ width }) => { + if (width > 0) { + setPopoverWidth(`${width}px`); + } + }, []); + + const handleSubmitButton = useCallback(() => { + setCommandToExecute(textEntered + rightOfCursor.text); + dispatch({ + type: 'updateInputTextEnteredState', + payload: { + textEntered: '', + rightOfCursor: undefined, + }, + }); + }, [dispatch, textEntered, rightOfCursor.text]); const handleOnChangeFocus = useCallback>( (hasFocus) => { @@ -134,8 +140,12 @@ export const CommandInput = memo(({ prompt = '', focusRef, .. if (keyCaptureFocusRef.current) { keyCaptureFocusRef.current.focus(); } + + if (isPopoverOpen) { + dispatch({ type: 'updateInputPopoverState', payload: { show: undefined } }); + } }, - [keyCaptureFocusRef] + [dispatch, isPopoverOpen, keyCaptureFocusRef] ); const handleInputCapture = useCallback( @@ -143,13 +153,12 @@ export const CommandInput = memo(({ prompt = '', focusRef, .. const keyCode = eventDetails.keyCode; // UP arrow key - // FIXME:PT to be addressed via OLM task #4384 - // if (keyCode === 38) { - // dispatch({ type: 'removeFocusFromKeyCapture' }); - // dispatch({ type: 'updateInputPopoverState', payload: { show: 'input-history' } }); - // - // return; - // } + if (keyCode === 38) { + dispatch({ type: 'removeFocusFromKeyCapture' }); + dispatch({ type: 'updateInputPopoverState', payload: { show: 'input-history' } }); + + return; + } // Update the store with the updated text that was entered dispatch({ @@ -217,65 +226,59 @@ export const CommandInput = memo(({ prompt = '', focusRef, .. return ( - - - {prompt && ( - - {prompt} - - )} - - + {(resizeRef) => { + return ( + - - -
{textEntered}
+ + {prompt && ( + + {prompt} + + )} + + + + +
{textEntered}
+
+ + + + +
+ {rightOfCursor.text} +
+
+
+
+
- - - -
- {rightOfCursor.text} -
+
-
- -
- - - -
-
+ + ); + }} +
); }); diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/command_input_clear_history.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/command_input_clear_history.tsx new file mode 100644 index 0000000000000..24bcd49959657 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/command_input_clear_history.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, { memo, useCallback, useState } from 'react'; +import { EuiButtonEmpty, EuiConfirmModal, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator'; +import { useDataTestSubj } from '../../../hooks/state_selectors/use_data_test_subj'; +import { useConsoleStateDispatch } from '../../../hooks/state_selectors/use_console_state_dispatch'; + +export const CommandInputClearHistory = memo(() => { + const [showConfirmModal, setShowConfirmModal] = useState(false); + const dispatch = useConsoleStateDispatch(); + const getTestId = useTestIdGenerator(useDataTestSubj()); + + const handleClearInputHistory = useCallback(() => { + setShowConfirmModal(true); + }, []); + + const handleConfirmModalCancel = useCallback(() => { + setShowConfirmModal(false); + }, []); + + const handleConfirmModalConfirm = useCallback(() => { + dispatch({ type: 'clearInputHistoryState' }); + setShowConfirmModal(false); + }, [dispatch]); + + return ( + <> + {showConfirmModal && ( + + + + )} + + + + + + + + + + ); +}); +CommandInputClearHistory.displayName = 'CommandInputClearHistory'; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/command_input_history.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/command_input_history.tsx index ba1e4eb610ea5..fbb5b7360eddd 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/command_input_history.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/command_input_history.tsx @@ -7,7 +7,7 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { EuiSelectableOption, EuiSelectableProps } from '@elastic/eui'; -import { EuiSelectable } from '@elastic/eui'; +import { EuiSelectable, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { InputHistoryItem } from '../../console_state/types'; import { useTestIdGenerator } from '../../../../../hooks/use_test_id_generator'; @@ -16,11 +16,22 @@ import { UserCommandInput } from '../../user_command_input'; import { useConsoleStateDispatch } from '../../../hooks/state_selectors/use_console_state_dispatch'; import { useWithInputHistory } from '../../../hooks/state_selectors/use_with_input_history'; import { useDataTestSubj } from '../../../hooks/state_selectors/use_data_test_subj'; +import { CommandInputClearHistory } from './command_input_clear_history'; -const NO_HISTORY_EMPTY_MESSAGE = i18n.translate( +export const NO_HISTORY_EMPTY_MESSAGE = i18n.translate( 'xpack.securitySolution.commandInputHistory.noHistoryEmptyMessage', { defaultMessage: 'No commands have been entered' } ); +const FILTER_HISTORY_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.commandInputHistory.filterPlaceholder', + { + defaultMessage: 'Filter previously entered actions', + } +); +const NO_FILTERED_MATCHES = i18n.translate( + 'xpack.securitySolution.commandInputHistory.noFilteredMatchesFoundMessage', + { defaultMessage: 'No entries found matching the filter entered' } +); export const CommandInputHistory = memo(() => { const dispatch = useConsoleStateDispatch(); @@ -42,22 +53,37 @@ export const CommandInputHistory = memo(() => { const selectableListProps: EuiSelectableProps['listProps'] = useMemo(() => { return { showIcons: false, - onKeyDownCapture: (ev) => { - // Works around an issue where the `enter` key event from the EuiSelectable gets capture - // by the outer `KeyCapture` input somehow. Likely due to the sequence of events between - // keyup, focus and the Focus trap component having the `returnFocus` on by default - if (ev.key === 'Enter') { - // @ts-expect-error - ev._CONSOLE_IGNORE_KEY = true; - } - }, + bordered: true, }; }, []); - const renderSelectionContent: EuiSelectableProps['children'] = useCallback((list, search) => { - return list; + const selectableSearchProps = useMemo(() => { + return { + placeholder: FILTER_HISTORY_PLACEHOLDER, + compressed: true, + fullWidth: true, + }; }, []); + const renderSelectionContent: EuiSelectableProps['children'] = useCallback( + (list, search) => { + return ( + <> + {list} + + {/* + The empty DIV below helps with a strange behaviour around losing the focus from inside the + popover's Portal. Normally, the `search` input will force the focus to behave as expected, but + if no input history exists, we don't want to show the search bar. In that case, we insert this + div with `tabindex` which seems to get around the issue. + */} + {inputHistory.length > 0 ? search :
} + + ); + }, + [inputHistory.length] + ); + const handleSelectableOnChange: EuiSelectableProps['onChange'] = useCallback( (items: EuiSelectableOption[]) => { optionWasSelected.current = true; @@ -115,18 +141,26 @@ export const CommandInputHistory = memo(() => { }, [dispatch, optionWasSelected, priorInputState]); return ( - - {renderSelectionContent} - +
+ {inputHistory.length > 0 && } + + + {renderSelectionContent} + +
); }); CommandInputHistory.displayName = 'CommandInputHistory'; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/input_area_popover.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/input_area_popover.tsx index 38043eeff2399..854ce85d348db 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/input_area_popover.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/components/input_area_popover.tsx @@ -30,6 +30,8 @@ export const InputAreaPopover = memo(({ children, width = const popoverPanelStyles = useMemo(() => { return { width, + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, }; }, [width]); diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/hooks/use_input_hints.ts b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/hooks/use_input_hints.ts index c8f4f00d0b758..8d179708f3850 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/hooks/use_input_hints.ts +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/hooks/use_input_hints.ts @@ -7,6 +7,7 @@ import { useEffect, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; +import { useWithInputTextEntered } from '../../../hooks/state_selectors/use_with_input_text_entered'; import { getArgumentsForCommand } from '../../../service/parsed_command_input'; import type { CommandDefinition } from '../../..'; import { useConsoleStateDispatch } from '../../../hooks/state_selectors/use_console_state_dispatch'; @@ -24,6 +25,11 @@ const NO_ARGUMENTS_HINT = i18n.translate('xpack.securitySolution.useInputHints.n defaultMessage: 'Hit enter to execute', }); +export const UP_ARROW_ACCESS_HISTORY_HINT = i18n.translate( + 'xpack.securitySolution.useInputHints.viewInputHistory', + { defaultMessage: 'Press the up arrow key to access previously entered commands' } +); + /** * Auto-generates console footer "hints" while user is interacting with the input area */ @@ -32,6 +38,7 @@ export const useInputHints = () => { const isInputPopoverOpen = Boolean(useWithInputShowPopover()); const commandEntered = useWithInputCommandEntered(); const commandList = useWithCommandList(); + const { textEntered } = useWithInputTextEntered(); const commandEnteredDefinition = useMemo(() => { if (commandEntered) { @@ -95,8 +102,13 @@ export const useInputHints = () => { dispatch({ type: 'setInputState', payload: { value: 'error' } }); } } else { - dispatch({ type: 'updateFooterContent', payload: { value: '' } }); + dispatch({ + type: 'updateFooterContent', + payload: { + value: textEntered || isInputPopoverOpen ? '' : UP_ARROW_ACCESS_HISTORY_HINT, + }, + }); dispatch({ type: 'setInputState', payload: { value: undefined } }); } - }, [commandEntered, commandEnteredDefinition, dispatch, isInputPopoverOpen]); + }, [commandEntered, commandEnteredDefinition, dispatch, isInputPopoverOpen, textEntered]); }; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/command_input.test.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/integration_tests/command_input.test.tsx similarity index 82% rename from x-pack/plugins/security_solution/public/management/components/console/components/command_input/command_input.test.tsx rename to x-pack/plugins/security_solution/public/management/components/console/components/command_input/integration_tests/command_input.test.tsx index 9abfda11f1371..54b8a487d2e09 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/command_input/command_input.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_input/integration_tests/command_input.test.tsx @@ -5,13 +5,15 @@ * 2.0. */ -import type { AppContextTestRender } from '../../../../../common/mock/endpoint'; -import type { ConsoleTestSetup } from '../../mocks'; -import { getConsoleTestSetup } from '../../mocks'; -import type { ConsoleProps } from '../../types'; -import { INPUT_DEFAULT_PLACEHOLDER_TEXT } from '../console_state/state_update_handlers/handle_input_area_state'; +import type { AppContextTestRender } from '../../../../../../common/mock/endpoint'; +import type { ConsoleTestSetup } from '../../../mocks'; +import { getConsoleTestSetup } from '../../../mocks'; +import type { ConsoleProps } from '../../../types'; +import { INPUT_DEFAULT_PLACEHOLDER_TEXT } from '../../console_state/state_update_handlers/handle_input_area_state'; import { act, waitFor, createEvent, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { NO_HISTORY_EMPTY_MESSAGE } from '../components/command_input_history'; +import { UP_ARROW_ACCESS_HISTORY_HINT } from '../hooks/use_input_hints'; describe('When entering data into the Console input', () => { let render: (props?: Partial) => ReturnType; @@ -90,10 +92,10 @@ describe('When entering data into the Console input', () => { expect(renderResult.queryByTestId('test-inputPlaceholder')).toBeNull(); }); - it('should NOT display any hint text in footer if nothing is displayed', () => { + it('should display default hint when nothing is typed into the command input area', () => { render(); - expect(getFooterText()?.trim()).toBe(''); + expect(getFooterText()?.trim()).toBe(UP_ARROW_ACCESS_HISTORY_HINT); }); it('should display hint when a known command is typed', () => { @@ -154,16 +156,24 @@ describe('When entering data into the Console input', () => { expect(renderResult.getByTestId('test-userCommandText').textContent).toEqual('isolate'); }); - // FIXME:PT uncomment once task OLM task #4384 is implemented - it.skip('should display the input history popover when UP key is pressed', async () => { + it('should display the input history popover when UP key is pressed', async () => { render(); await showInputHistoryPopover(); expect(renderResult.getByTestId('test-inputHistorySelector')).not.toBeNull(); }); - // FIXME:PT uncomment once task OLM task #4384 is implemented - describe.skip('and when the command input history popover is opened', () => { + it('should hide the history popover if user clicks back on input area', async () => { + render(); + await showInputHistoryPopover(); + userEvent.click(renderResult.getByTestId('test-keyCapture-input')); + + await waitFor(() => { + expect(renderResult.queryByTestId('test-inputHistorySelector')).toBeNull(); + }); + }); + + describe('and when the command input history popover is opened', () => { const renderWithInputHistory = async (inputText: string = '') => { render(); enterCommand('help'); @@ -214,6 +224,45 @@ describe('When entering data into the Console input', () => { expect(getLeftOfCursorText()).toEqual('cmd1 --help'); }); }); + + it('should show confirm dialog when Clear history button is clicked', async () => { + await renderWithInputHistory('one'); + + userEvent.click(renderResult.getByTestId('test-clearInputHistoryButton')); + + await waitFor(() => { + expect(renderResult.getByTestId('confirmModalTitleText')); + }); + }); + + describe('and clear history confirm dialog is displayed', () => { + beforeEach(async () => { + await renderWithInputHistory('one'); + userEvent.click(renderResult.getByTestId('test-clearInputHistoryButton')); + await waitFor(() => { + expect(renderResult.getByTestId('confirmModalTitleText')); + }); + }); + + it('should close the confirm modal if Cancel button is clicked', async () => { + userEvent.click(renderResult.getByTestId('confirmModalCancelButton')); + + await waitFor(() => { + expect(renderResult.queryByTestId('confirmModalTitleText')).toBeNull(); + expect(renderResult.getByTestId('test-inputHistorySelector')).not.toBeNull(); + }); + }); + + it('should clear all input history if Clear button is clicked', async () => { + userEvent.click(renderResult.getByTestId('confirmModalConfirmButton')); + + await waitFor(() => { + expect(renderResult.getByTestId('euiSelectableMessage')).toHaveTextContent( + NO_HISTORY_EMPTY_MESSAGE + ); + }); + }); + }); }); describe('and keyboard special keys are pressed', () => { @@ -364,8 +413,7 @@ describe('When entering data into the Console input', () => { expect(selection!.toString()).toEqual('isolate'); }); - // FIXME:PT uncomment once task OLM task #4384 is implemented - it.skip('should return original cursor position if input history is closed with no selection', async () => { + it('should return original cursor position if input history is closed with no selection', async () => { typeKeyboardKey('{Enter}'); // add `isolate` to the input history typeKeyboardKey('release'); @@ -390,8 +438,7 @@ describe('When entering data into the Console input', () => { expect(getRightOfCursorText()).toEqual('elease'); }); - // FIXME:PT uncomment once task OLM task #4384 is implemented - it.skip('should reset cursor position to default (at end) if a selection is done from input history', async () => { + it('should reset cursor position to default (at end) if a selection is done from input history', async () => { typeKeyboardKey('{Enter}'); // add `isolate` to the input history typeKeyboardKey('release'); diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/console_state.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/console_state.tsx index c533e754a112a..e486b3bf7a97d 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/console_state.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/console_state.tsx @@ -7,6 +7,10 @@ import type { PropsWithChildren } from 'react'; import React, { useReducer, memo, createContext, useContext, useEffect, useCallback } from 'react'; +import { + useSaveInputHistoryToStorage, + useStoredInputHistory, +} from './hooks/use_stored_input_history'; import { useWithManagedConsoleState } from '../console_manager/console_manager'; import type { InitialStateInterface } from './state_reducer'; import { initiateState, stateDataReducer } from './state_reducer'; @@ -20,19 +24,37 @@ type ConsoleStateProviderProps = PropsWithChildren<{}> & InitialStateInterface; * A Console wide data store for internal state management between inner components */ export const ConsoleStateProvider = memo( - ({ commands, scrollToBottom, keyCapture, HelpComponent, dataTestSubj, managedKey, children }) => { + ({ + commands, + scrollToBottom, + keyCapture, + HelpComponent, + dataTestSubj, + storagePrefix, + managedKey, + children, + }) => { const [getConsoleState, storeConsoleState] = useWithManagedConsoleState(managedKey); + const storedInputHistoryData = useStoredInputHistory(storagePrefix); + const saveInputHistoryData = useSaveInputHistoryToStorage(storagePrefix); const stateInitializer = useCallback( (stateInit: InitialStateInterface): ConsoleDataState => { - return initiateState(stateInit, getConsoleState ? getConsoleState() : undefined); + const createdInitState = initiateState( + stateInit, + getConsoleState ? getConsoleState() : undefined + ); + + createdInitState.input.history = storedInputHistoryData; + + return createdInitState; }, - [getConsoleState] + [getConsoleState, storedInputHistoryData] ); const [state, dispatch] = useReducer( stateDataReducer, - { commands, scrollToBottom, keyCapture, HelpComponent, dataTestSubj }, + { commands, scrollToBottom, keyCapture, HelpComponent, dataTestSubj, storagePrefix }, stateInitializer ); @@ -45,6 +67,14 @@ export const ConsoleStateProvider = memo( } }, [state, storeConsoleState]); + // Anytime `input.history` changes and a `storagePrefix` is defined, then persist + // the input history to storage + useEffect(() => { + if (storagePrefix && state.input.history) { + saveInputHistoryData(state.input.history); + } + }, [saveInputHistoryData, state.input.history, storagePrefix]); + return ( {children} diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/hooks/use_stored_input_history.ts b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/hooks/use_stored_input_history.ts new file mode 100644 index 0000000000000..8a9249b3dc01f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/hooks/use_stored_input_history.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 { useCallback, useMemo } from 'react'; +import { useKibana } from '../../../../../../common/lib/kibana'; +import type { ConsoleDataState } from '../types'; + +interface InputHistoryOfflineStorage { + version: number; + data: ConsoleDataState['input']['history']; +} + +const COMMAND_INPUT_HISTORY_KEY = 'commandInputHistory'; + +/** + * The current version of the input history offline storage. Will help in the future + * if we ever need to "migrate" stored data to a new format + */ +const CURRENT_VERSION = 1; + +const getDefaultInputHistoryStorage = (): InputHistoryOfflineStorage => { + return { + version: CURRENT_VERSION, + data: [], + }; +}; + +export const useStoredInputHistory = ( + storagePrefix: ConsoleDataState['storagePrefix'] +): InputHistoryOfflineStorage['data'] => { + const { storage } = useKibana().services; + + return useMemo(() => { + if (storagePrefix) { + const storedData = + (storage.get( + `${storagePrefix}.${COMMAND_INPUT_HISTORY_KEY}` + ) as InputHistoryOfflineStorage) ?? getDefaultInputHistoryStorage(); + + return storedData.data; + } + + return []; + }, [storage, storagePrefix]); +}; + +type SaveInputHistoryToStorageCallback = (data: InputHistoryOfflineStorage['data']) => void; + +export const useSaveInputHistoryToStorage = ( + storagePrefix: ConsoleDataState['storagePrefix'] +): SaveInputHistoryToStorageCallback => { + const { storage } = useKibana().services; + + return useCallback( + (data: InputHistoryOfflineStorage['data']) => { + if (storagePrefix) { + const update: InputHistoryOfflineStorage = { + version: CURRENT_VERSION, + data, + }; + + storage.set(`${storagePrefix}.${COMMAND_INPUT_HISTORY_KEY}`, update); + } + }, + [storage, storagePrefix] + ); +}; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_reducer.ts b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_reducer.ts index 6436d619e9619..d6c6161da3300 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_reducer.ts +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_reducer.ts @@ -17,7 +17,13 @@ import { getBuiltinCommands } from '../../service/builtin_commands'; export type InitialStateInterface = Pick< ConsoleDataState, - 'commands' | 'scrollToBottom' | 'dataTestSubj' | 'HelpComponent' | 'managedKey' | 'keyCapture' + | 'commands' + | 'scrollToBottom' + | 'dataTestSubj' + | 'HelpComponent' + | 'managedKey' + | 'keyCapture' + | 'storagePrefix' >; export const initiateState = ( @@ -92,6 +98,7 @@ export const stateDataReducer: ConsoleStoreReducer = (state, action) => { case 'updateInputPopoverState': case 'updateInputHistoryState': + case 'clearInputHistoryState': case 'updateInputTextEnteredState': case 'updateInputPlaceholderState': case 'setInputState': diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_input_area_state.ts b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_input_area_state.ts index 869a09963fd71..092f74dc66ce1 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_input_area_state.ts +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_input_area_state.ts @@ -21,6 +21,7 @@ type InputAreaStateAction = ConsoleDataAction & { type: | 'updateInputPopoverState' | 'updateInputHistoryState' + | 'clearInputHistoryState' | 'updateInputTextEnteredState' | 'updateInputPlaceholderState' | 'setInputState'; @@ -53,6 +54,15 @@ export const handleInputAreaState: ConsoleStoreReducer = ( }, }; + case 'clearInputHistoryState': + return { + ...state, + input: { + ...state.input, + history: [], + }, + }; + case 'updateInputTextEnteredState': const { textEntered: newTextEntered, rightOfCursor: newRightOfCursor = { text: '' } } = typeof payload === 'function' diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/types.ts b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/types.ts index 00681edf62488..291f525196bba 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/types.ts +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/types.ts @@ -36,6 +36,9 @@ export interface ConsoleDataState { dataTestSubj?: string; + /** The local storage prefix for saving/persisting data associated with the console */ + storagePrefix?: string; + /** The key for the console when it is under ConsoleManager control */ managedKey?: symbol; @@ -145,6 +148,10 @@ export type ConsoleDataAction = payload: { command: string; }; + } + | { + type: 'clearInputHistoryState'; + payload?: never; }; type PayloadValueOrFunction = T | ((options: Required) => T); diff --git a/x-pack/plugins/security_solution/public/management/components/console/console.tsx b/x-pack/plugins/security_solution/public/management/components/console/console.tsx index a46c33d457ba7..f1736011ae23e 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/console.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/console.tsx @@ -105,7 +105,15 @@ const ConsoleWindow = styled.div` `; export const Console = memo( - ({ prompt, commands, HelpComponent, TitleComponent, managedKey, ...commonProps }) => { + ({ + prompt, + commands, + HelpComponent, + TitleComponent, + storagePrefix, + managedKey, + ...commonProps + }) => { const scrollingViewport = useRef(null); const inputFocusRef: CommandInputProps['focusRef'] = useRef(null); const getTestId = useTestIdGenerator(commonProps['data-test-subj']); @@ -145,6 +153,7 @@ export const Console = memo( managedKey={managedKey} HelpComponent={HelpComponent} dataTestSubj={commonProps['data-test-subj']} + storagePrefix={storagePrefix} > diff --git a/x-pack/plugins/security_solution/public/management/components/console/hooks/use_storage_key.ts b/x-pack/plugins/security_solution/public/management/components/console/hooks/use_storage_key.ts new file mode 100644 index 0000000000000..45fa8a205407f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/console/hooks/use_storage_key.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMemo } from 'react'; +import { useConsoleStore } from '../components/console_state/console_state'; + +export const useStorageKey = (suffix: string): string | undefined => { + const prefix = useConsoleStore().state.storagePrefix; + + return useMemo(() => { + return prefix ? `${prefix}.${suffix}` : undefined; + }, [prefix, suffix]); +}; diff --git a/x-pack/plugins/security_solution/public/management/components/console/types.ts b/x-pack/plugins/security_solution/public/management/components/console/types.ts index ef9191bbad522..9102d77d66d09 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/types.ts +++ b/x-pack/plugins/security_solution/public/management/components/console/types.ts @@ -194,8 +194,16 @@ export interface ConsoleProps extends CommonProps { */ TitleComponent?: ComponentType; + /** The string to display to the left of the input area */ prompt?: string; + /** + * If defined, certain console data (ex. command input history) will be persisted to localstorage + * using this prefix as part of the storage key. That data will then be retrieved and reused + * across all console windows + */ + storagePrefix?: string; + /** * For internal use only! * Provided by the ConsoleManager to indicate that the console is being managed by it diff --git a/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx b/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx index 59e5286167bc5..81ba36e91e353 100644 --- a/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx +++ b/x-pack/plugins/security_solution/public/management/components/page_overlay/page_overlay.tsx @@ -17,6 +17,7 @@ import { useIsMounted } from '@kbn/securitysolution-hook-utils'; import { useHasFullScreenContent } from '../../../common/containers/use_full_screen'; import { FULL_SCREEN_CONTENT_OVERRIDES_CSS_STYLESHEET, + TIMELINE_EUI_POPOVER_PANEL_ZINDEX, TIMELINE_OVERRIDES_CSS_STYLESHEET, } from '../../../common/components/page'; import { @@ -80,12 +81,25 @@ const PAGE_OVERLAY_CSS_CLASSNAME = 'securitySolution-pageOverlay'; export const PAGE_OVERLAY_DOCUMENT_BODY_IS_VISIBLE_CLASSNAME = `${PAGE_OVERLAY_CSS_CLASSNAME}-isVisible`; export const PAGE_OVERLAY_DOCUMENT_BODY_LOCK_CLASSNAME = `${PAGE_OVERLAY_CSS_CLASSNAME}-lock`; export const PAGE_OVERLAY_DOCUMENT_BODY_FULLSCREEN_CLASSNAME = `${PAGE_OVERLAY_CSS_CLASSNAME}-fullScreen`; +export const PAGE_OVERLAY_DOCUMENT_BODY_OVER_PAGE_WRAPPER_CLASSNAME = `${PAGE_OVERLAY_CSS_CLASSNAME}-overSecuritySolutionPageWrapper`; const PageOverlayGlobalStyles = createGlobalStyle<{ theme: EuiTheme }>` body.${PAGE_OVERLAY_DOCUMENT_BODY_LOCK_CLASSNAME} { overflow: hidden; } + //------------------------------------------------------------------------------------------- + // Style overrides for when Page Overlay is shown over SecuritySolutionPageWrapper component + //------------------------------------------------------------------------------------------- + // That page wrapper includes several global EUI styles that can conflict with content shown + // from inside of this Page Overlay component. + //------------------------------------------------------------------------------------------- + // Eui Confirm Dialog mask overlay should be displayed above any other popovers + //------------------------------------------------------------------------------------------- + body.${PAGE_OVERLAY_DOCUMENT_BODY_OVER_PAGE_WRAPPER_CLASSNAME} .euiOverlayMask[data-relative-to-header="above"] { + z-index: ${TIMELINE_EUI_POPOVER_PANEL_ZINDEX}; + } + //------------------------------------------------------------------------------------------- // Style overrides for when Page Overlay is in full screen mode //------------------------------------------------------------------------------------------- @@ -112,6 +126,11 @@ const PageOverlayGlobalStyles = createGlobalStyle<{ theme: EuiTheme }>` z-index: ${({ theme: { eui } }) => eui[TIMELINE_EUI_THEME_ZINDEX_LEVEL]}; } + // Confirm Dialog mask overlay should be displayed above any other popover + .euiOverlayMask[data-relative-to-header="above"] { + z-index: ${TIMELINE_EUI_POPOVER_PANEL_ZINDEX}; + } + // Other Timeline overrides from AppGlobalStyle: // x-pack/plugins/security_solution/public/common/components/page/index.tsx ${TIMELINE_OVERRIDES_CSS_STYLESHEET} @@ -142,6 +161,14 @@ const unSetDocumentBodyFullScreen = () => { document.body.classList.remove(PAGE_OVERLAY_DOCUMENT_BODY_FULLSCREEN_CLASSNAME); }; +const setDocumentBodyOverPageWrapper = () => { + document.body.classList.add(PAGE_OVERLAY_DOCUMENT_BODY_OVER_PAGE_WRAPPER_CLASSNAME); +}; + +const unSetDocumentBodyOverPageWrapper = () => { + document.body.classList.remove(PAGE_OVERLAY_DOCUMENT_BODY_OVER_PAGE_WRAPPER_CLASSNAME); +}; + export interface PageOverlayProps { children: ReactNode; @@ -285,10 +312,15 @@ export const PageOverlay = memo( // Handle adding class names to the `document.body` DOM element useEffect(() => { if (isMounted()) { + const isOverSecuritySolutionPageWrapper = Boolean( + document.querySelector('.securitySolutionWrapper') + ); + if (isHidden) { unSetDocumentBodyOverlayIsVisible(); unSetDocumentBodyLock(); unSetDocumentBodyFullScreen(); + unSetDocumentBodyOverPageWrapper(); } else { setDocumentBodyOverlayIsVisible(); @@ -299,6 +331,10 @@ export const PageOverlay = memo( if (showInFullScreen) { setDocumentBodyFullScreen(); } + + if (isOverSecuritySolutionPageWrapper) { + setDocumentBodyOverPageWrapper(); + } } } @@ -306,6 +342,7 @@ export const PageOverlay = memo( unSetDocumentBodyLock(); unSetDocumentBodyOverlayIsVisible(); unSetDocumentBodyFullScreen(); + unSetDocumentBodyOverPageWrapper(); }; }, [isHidden, isMounted, lockDocumentBody, showInFullScreen]); diff --git a/x-pack/plugins/security_solution/public/management/hooks/use_with_show_endpoint_responder.tsx b/x-pack/plugins/security_solution/public/management/hooks/use_with_show_endpoint_responder.tsx index bfd6749608603..c5dd32bacfb8f 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/use_with_show_endpoint_responder.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/use_with_show_endpoint_responder.tsx @@ -54,6 +54,7 @@ export const useWithShowEndpointResponder = (): ShowEndpointResponseActionsConso endpointPrivileges, }), 'data-test-subj': 'endpointResponseActionsConsole', + storagePrefix: 'xpack.securitySolution.Responder', TitleComponent: () => , }, PageTitleComponent: () => <>{RESPONDER_PAGE_TITLE}, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx index 273c6f30947eb..32ab4d7d50adf 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx @@ -55,9 +55,17 @@ jest.mock('../../containers/all', () => { useGetAllTimeline: jest.fn(), }; }); - -jest.mock('../../../common/lib/kibana'); -jest.mock('../../../common/components/link_to'); +const mockNavigateTo = jest.fn(); +jest.mock('../../../common/lib/kibana', () => { + const actual = jest.requireActual('../../../common/lib/kibana'); + return { + ...actual, + useNavigation: () => ({ + getAppUrl: jest.fn(), + navigateTo: mockNavigateTo, + }), + }; +}); jest.mock('../../../common/components/link_to', () => { const originalModule = jest.requireActual('../../../common/components/link_to'); @@ -656,4 +664,20 @@ describe('StatefulOpenTimeline', () => { expect((queryTimelineById as jest.Mock).mock.calls[0][0].duplicate).toEqual(true); }); }); + + test('navigates to create rule page with timeline id in URL when Create rule from timeline click', async () => { + const wrapper = mount( + + + + ); + + wrapper.find('[data-test-subj="euiCollapsedItemActionsButton"]').first().simulate('click'); + wrapper.find('[data-test-subj="create-rule-from-timeline"]').first().simulate('click'); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index 628d2bf14d5e5..970a6482065af 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -8,6 +8,10 @@ import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; +import { RULE_FROM_TIMELINE_URL_PARAM } from '../../../detections/containers/detection_engine/rules/use_rule_from_timeline'; +import { encodeRisonUrlState } from '../../../common/utils/global_query_string/helpers'; +import { useNavigation } from '../../../common/lib/kibana'; +import { SecurityPageName } from '../../../../common/constants'; import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; import type { SortFieldTimeline } from '../../../../common/types/timeline'; import { TimelineId } from '../../../../common/types/timeline'; @@ -40,6 +44,7 @@ import type { OpenTimelineResult, OnToggleShowNotes, OnDeleteOneTimeline, + OnCreateRuleFromTimeline, } from './types'; import { DEFAULT_SORT_FIELD, DEFAULT_SORT_DIRECTION } from './constants'; import { useTimelineTypes } from './use_timeline_types'; @@ -273,6 +278,15 @@ export const StatefulOpenTimelineComponent = React.memo( }, [] ); + const { navigateTo } = useNavigation(); + const onCreateRule: OnCreateRuleFromTimeline = useCallback( + (savedObjectId) => + navigateTo({ + deepLinkId: SecurityPageName.rulesCreate, + path: `?${RULE_FROM_TIMELINE_URL_PARAM}=${encodeRisonUrlState(savedObjectId)}`, + }), + [navigateTo] + ); /** Resets the selection state such that all timelines are unselected */ const resetSelectionState = useCallback(() => { @@ -324,6 +338,7 @@ export const StatefulOpenTimelineComponent = React.memo( itemIdToExpandedNotesRowMap={itemIdToExpandedNotesRowMap} importDataModalToggle={importDataModalToggle} onAddTimelinesToFavorites={undefined} + onCreateRule={onCreateRule} onDeleteSelected={onDeleteSelected} onlyFavorites={onlyFavorites} onOpenTimeline={openTimeline} diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx index 05aa0c105d5a3..fe673db139042 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx @@ -411,6 +411,22 @@ describe('OpenTimeline', () => { ).toEqual(['createFrom', 'duplicate', 'export', 'selectable', 'delete']); }); + test('it should include createRule in timeline actions if onCreateRule is passed', () => { + const defaultProps = { + ...getDefaultTestProps(mockResults), + timelineStatus: TimelineStatus.active, + }; + const wrapper = mountWithIntl( + + + + ); + + expect( + wrapper.find('[data-test-subj="timelines-table"]').first().prop('actionTimelineToShow') + ).toEqual(['createFrom', 'duplicate', 'createRule', 'export', 'selectable', 'delete']); + }); + test("it should render selected count if timelineStatus is active (selecting custom templates' tab)", () => { const defaultProps = { ...getDefaultTestProps(mockResults), diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx index 8fee5fcc0da3b..9d3ecf0492806 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx @@ -44,6 +44,7 @@ export const OpenTimeline = React.memo( isLoading, itemIdToExpandedNotesRowMap, importDataModalToggle, + onCreateRule, onDeleteSelected, onlyFavorites, onOpenTimeline, @@ -148,7 +149,12 @@ export const OpenTimeline = React.memo( }, [setImportDataModalToggle, refetch]); const actionTimelineToShow = useMemo(() => { - const timelineActions: ActionTimelineToShow[] = ['createFrom', 'duplicate']; + const createRule: ActionTimelineToShow[] = ['createRule']; + const timelineActions: ActionTimelineToShow[] = [ + 'createFrom', + 'duplicate', + ...(onCreateRule != null ? createRule : []), + ]; if (timelineStatus !== TimelineStatus.immutable) { timelineActions.push('export'); @@ -164,7 +170,7 @@ export const OpenTimeline = React.memo( } return timelineActions; - }, [onDeleteSelected, deleteTimelines, timelineStatus]); + }, [onCreateRule, timelineStatus, onDeleteSelected, deleteTimelines]); const SearchRowContent = useMemo(() => <>{templateTimelineFilter}, [templateTimelineFilter]); @@ -257,6 +263,7 @@ export const OpenTimeline = React.memo( loading={isLoading} itemIdToExpandedNotesRowMap={itemIdToExpandedNotesRowMap} enableExportTimelineDownloader={enableExportTimelineDownloader} + onCreateRule={onCreateRule} onOpenDeleteTimelineModal={onOpenDeleteTimelineModal} onOpenTimeline={onOpenTimeline} onSelectionChange={onSelectionChange} diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx index 91785b05c5bf5..00cec44b290be 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.test.tsx @@ -15,7 +15,16 @@ import { useGetAllTimeline, getAllTimeline } from '../../../containers/all'; import { useTimelineStatus } from '../use_timeline_status'; import { OpenTimelineModal } from '.'; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../common/lib/kibana', () => { + const actual = jest.requireActual('../../../../common/lib/kibana'); + return { + ...actual, + useNavigation: jest.fn().mockReturnValue({ + getAppUrl: jest.fn(), + navigateTo: jest.fn(), + }), + }; +}); jest.mock('../../../containers/all', () => { const originalModule = jest.requireActual('../../../containers/all'); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx index 2cbccd3cb2355..29d3c560cc74c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx @@ -9,6 +9,7 @@ import type { ActionTimelineToShow, DeleteTimelines, EnableExportTimelineDownloader, + OnCreateRuleFromTimeline, OnOpenTimeline, OpenTimelineResult, OnOpenDeleteTimelineModal, @@ -16,7 +17,6 @@ import type { } from '../types'; import * as i18n from '../translations'; import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; - /** * Returns the action columns (e.g. delete, open duplicate timeline) */ @@ -26,12 +26,14 @@ export const getActionsColumns = ({ enableExportTimelineDownloader, onOpenDeleteTimelineModal, onOpenTimeline, + onCreateRule, }: { actionTimelineToShow: ActionTimelineToShow[]; deleteTimelines?: DeleteTimelines; enableExportTimelineDownloader?: EnableExportTimelineDownloader; onOpenDeleteTimelineModal?: OnOpenDeleteTimelineModal; onOpenTimeline: OnOpenTimeline; + onCreateRule?: OnCreateRuleFromTimeline; }): [TimelineActionsOverflowColumns] => { const createTimelineFromTemplate = { name: i18n.CREATE_TIMELINE_FROM_TEMPLATE, @@ -132,6 +134,22 @@ export const getActionsColumns = ({ available: () => actionTimelineToShow.includes('delete') && deleteTimelines != null, }; + const createRuleFromTimeline = { + name: i18n.CREATE_RULE_FROM_TIMELINE, + icon: 'indexEdit', + onClick: (selectedTimeline: OpenTimelineResult) => { + if (onCreateRule != null && selectedTimeline.savedObjectId) + onCreateRule(selectedTimeline.savedObjectId); + }, + enabled: (timeline: OpenTimelineResult) => + onCreateRule != null && + timeline.savedObjectId != null && + timeline.status !== TimelineStatus.immutable, + description: i18n.CREATE_RULE_FROM_TIMELINE, + 'data-test-subj': 'create-rule-from-timeline', + available: () => actionTimelineToShow.includes('createRule') && onCreateRule != null, + }; + return [ { width: '80px', @@ -142,6 +160,7 @@ export const getActionsColumns = ({ openAsDuplicateTemplateColumn, exportTimelineAction, deleteTimelineColumn, + createRuleFromTimeline, ], }, ]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx index 32a63bdf6c5b6..9dfc3d1670b0d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx @@ -13,6 +13,7 @@ import * as i18n from '../translations'; import type { ActionTimelineToShow, DeleteTimelines, + OnCreateRuleFromTimeline, OnOpenTimeline, OnSelectionChange, OnTableChange, @@ -54,6 +55,7 @@ export const getTimelinesTableColumns = ({ deleteTimelines, enableExportTimelineDownloader, itemIdToExpandedNotesRowMap, + onCreateRule, onOpenDeleteTimelineModal, onOpenTimeline, onToggleShowNotes, @@ -64,6 +66,7 @@ export const getTimelinesTableColumns = ({ deleteTimelines?: DeleteTimelines; enableExportTimelineDownloader?: EnableExportTimelineDownloader; itemIdToExpandedNotesRowMap: Record; + onCreateRule?: OnCreateRuleFromTimeline; onOpenDeleteTimelineModal?: OnOpenDeleteTimelineModal; onOpenTimeline: OnOpenTimeline; onSelectionChange: OnSelectionChange; @@ -82,6 +85,7 @@ export const getTimelinesTableColumns = ({ ...getIconHeaderColumns({ timelineType }), ...(actionTimelineToShow.length ? getActionsColumns({ + onCreateRule, actionTimelineToShow, deleteTimelines, enableExportTimelineDownloader, @@ -99,6 +103,7 @@ export interface TimelinesTableProps { loading: boolean; itemIdToExpandedNotesRowMap: Record; enableExportTimelineDownloader?: EnableExportTimelineDownloader; + onCreateRule?: OnCreateRuleFromTimeline; onOpenDeleteTimelineModal?: OnOpenDeleteTimelineModal; onOpenTimeline: OnOpenTimeline; onSelectionChange: OnSelectionChange; @@ -128,6 +133,7 @@ export const TimelinesTable = React.memo( loading: isLoading, itemIdToExpandedNotesRowMap, enableExportTimelineDownloader, + onCreateRule, onOpenDeleteTimelineModal, onOpenTimeline, onSelectionChange, @@ -178,6 +184,7 @@ export const TimelinesTable = React.memo( deleteTimelines, itemIdToExpandedNotesRowMap, enableExportTimelineDownloader, + onCreateRule, onOpenDeleteTimelineModal, onOpenTimeline, onSelectionChange, @@ -190,6 +197,7 @@ export const TimelinesTable = React.memo( deleteTimelines, itemIdToExpandedNotesRowMap, enableExportTimelineDownloader, + onCreateRule, onOpenDeleteTimelineModal, onOpenTimeline, onSelectionChange, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts index 98e9300b9661a..a49d301b61154 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts @@ -116,6 +116,13 @@ export const ONLY_FAVORITES = i18n.translate( } ); +export const CREATE_RULE_FROM_TIMELINE = i18n.translate( + 'xpack.securitySolution.open.timeline.createRuleFromTimelineTooltip', + { + defaultMessage: 'Create rule from timeline', + } +); + export const CREATE_TEMPLATE_FROM_TIMELINE = i18n.translate( 'xpack.securitySolution.open.timeline.createTemplateFromTimelineTooltip', { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts index 625e30bb9727e..723d9ebc605df 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts @@ -78,6 +78,9 @@ export interface EuiSearchBarQuery { /** Performs IO to delete the specified timelines */ export type DeleteTimelines = (timelineIds: string[], variables?: AllTimelinesVariables) => void; +/** Invoked when the user clicks the action create rule from timeline */ +export type OnCreateRuleFromTimeline = (savedObjectId: string) => void; + /** Invoked when the user clicks the action make the selected timelines favorites */ export type OnAddTimelinesToFavorites = () => void; @@ -126,7 +129,13 @@ export interface OnTableChangeParams { /** Invoked by the EUI table implementation when the user interacts with the table */ export type OnTableChange = (tableChange: OnTableChangeParams) => void; -export type ActionTimelineToShow = 'createFrom' | 'duplicate' | 'delete' | 'export' | 'selectable'; +export type ActionTimelineToShow = + | 'createFrom' + | 'duplicate' + | 'delete' + | 'export' + | 'selectable' + | 'createRule'; export interface OpenTimelineProps { /** Invoked when the user clicks the delete (trash) icon on an individual timeline */ @@ -141,6 +150,8 @@ export interface OpenTimelineProps { itemIdToExpandedNotesRowMap: Record; /** Display import timelines modal*/ importDataModalToggle?: boolean; + /** If this callback is specified, a "Create rule from timeline" button will be displayed, and this callback will be invoked when the button is clicked */ + onCreateRule?: OnCreateRuleFromTimeline; /** If this callback is specified, a "Favorite Selected" button will be displayed, and this callback will be invoked when the button is clicked */ onAddTimelinesToFavorites?: OnAddTimelinesToFavorites; /** If this callback is specified, a "Delete Selected" button will be displayed, and this callback will be invoked when the button is clicked */ diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts index 822b59741f844..5b0d12579b5cb 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_private_locations.ts @@ -17,6 +17,7 @@ export const PrivateLocationCodec = t.intersection([ t.partial({ isServiceManaged: t.boolean, isInvalid: t.boolean, + tags: t.array(t.string), /* Empty Lat lon was accidentally saved as an empty string instead of undefined or null * Need a migration to fix */ geo: t.interface({ lat: t.union([t.string, t.number]), lon: t.union([t.string, t.number]) }), diff --git a/x-pack/plugins/synthetics/e2e/journeys/index.ts b/x-pack/plugins/synthetics/e2e/journeys/index.ts index 0e795fbe631f7..a5c002cfd6880 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/index.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/index.ts @@ -6,11 +6,11 @@ */ export * from './data_view_permissions'; +export * from './read_only_user'; export * from './synthetics'; export * from './alerts'; export * from './uptime.journey'; export * from './step_duration.journey'; -export * from './read_only_user'; export * from './monitor_details.journey'; export * from './monitor_name.journey'; export * from './monitor_management.journey'; diff --git a/x-pack/plugins/synthetics/e2e/journeys/monitor_details.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/monitor_details.journey.ts index 3ddf0cebd0cf3..644c1bbdc7891 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/monitor_details.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/monitor_details.journey.ts @@ -12,29 +12,19 @@ * 2.0. */ import uuid from 'uuid'; -import { journey, step, expect, after, before, Page } from '@elastic/synthetics'; +import { journey, step, expect, after, Page } from '@elastic/synthetics'; import { monitorManagementPageProvider } from '../page_objects/monitor_management'; journey('MonitorDetails', async ({ page, params }: { page: Page; params: any }) => { const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl }); const name = `Test monitor ${uuid.v4()}`; - before(async () => { - await uptime.waitForLoadingToFinish(); - }); - after(async () => { await uptime.enableMonitorManagement(false); }); step('Go to monitor-management', async () => { - await uptime.navigateToMonitorManagement(); - }); - - step('login to Kibana', async () => { - await uptime.loginToKibana(); - const invalid = await page.locator(`text=Username or password is incorrect. Please try again.`); - expect(await invalid.isVisible()).toBeFalsy(); + await uptime.navigateToMonitorManagement(true); }); step('create basic monitor', async () => { diff --git a/x-pack/plugins/synthetics/e2e/journeys/monitor_management.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/monitor_management.journey.ts index c3b325183b05a..7c263a9a64a7d 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/monitor_management.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/monitor_management.journey.ts @@ -6,7 +6,7 @@ */ import uuid from 'uuid'; -import { journey, step, expect, before, after, Page } from '@elastic/synthetics'; +import { journey, step, expect, after, Page } from '@elastic/synthetics'; import { byTestId } from '@kbn/observability-plugin/e2e/utils'; import { monitorManagementPageProvider } from '../page_objects/monitor_management'; import { DataStream } from '../../common/runtime_types/monitor_management'; @@ -99,25 +99,13 @@ const createMonitorJourney = ({ const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl }); const isRemote = process.env.SYNTHETICS_REMOTE_ENABLED; - before(async () => { - await uptime.waitForLoadingToFinish(); - }); - after(async () => { await uptime.navigateToMonitorManagement(); await uptime.enableMonitorManagement(false); }); step('Go to monitor-management', async () => { - await uptime.navigateToMonitorManagement(); - }); - - step('login to Kibana', async () => { - await uptime.loginToKibana(); - const invalid = await page.locator( - `text=Username or password is incorrect. Please try again.` - ); - expect(await invalid.isVisible()).toBeFalsy(); + await uptime.navigateToMonitorManagement(true); }); step(`create ${monitorType} monitor`, async () => { @@ -168,20 +156,12 @@ journey('Monitor Management breadcrumbs', async ({ page, params }: { page: Page; apmServiceName: 'service', }; - before(async () => { - await uptime.waitForLoadingToFinish(); - }); - after(async () => { await uptime.enableMonitorManagement(false); }); step('Go to monitor-management', async () => { - await uptime.navigateToMonitorManagement(); - }); - - step('login to Kibana', async () => { - await uptime.loginToKibana(); + await uptime.navigateToMonitorManagement(true); }); step('Check breadcrumb', async () => { @@ -243,10 +223,6 @@ journey( }), ]; - before(async () => { - await uptime.waitForLoadingToFinish(); - }); - after(async () => { await uptime.navigateToMonitorManagement(); await uptime.deleteMonitors(); @@ -254,15 +230,7 @@ journey( }); step('Go to monitor-management', async () => { - await uptime.navigateToMonitorManagement(); - }); - - step('login to Kibana', async () => { - await uptime.loginToKibana(); - const invalid = await page.locator( - `text=Username or password is incorrect. Please try again.` - ); - expect(await invalid.isVisible()).toBeFalsy(); + await uptime.navigateToMonitorManagement(true); }); for (const monitorConfig of sortedMonitors) { diff --git a/x-pack/plugins/synthetics/e2e/journeys/monitor_management_enablement.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/monitor_management_enablement.journey.ts index 5d22fe8491579..2d15ba620f370 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/monitor_management_enablement.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/monitor_management_enablement.journey.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { journey, step, expect, before, after, Page } from '@elastic/synthetics'; +import { journey, step, expect, after, Page } from '@elastic/synthetics'; import { monitorManagementPageProvider } from '../page_objects/monitor_management'; journey( @@ -12,24 +12,12 @@ journey( async ({ page, params }: { page: Page; params: any }) => { const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl }); - before(async () => { - await uptime.waitForLoadingToFinish(); - }); - after(async () => { await uptime.enableMonitorManagement(false); }); step('Go to monitor-management', async () => { - await uptime.navigateToMonitorManagement(); - }); - - step('login to Kibana', async () => { - await uptime.loginToKibana(); - const invalid = await page.locator( - `text=Username or password is incorrect. Please try again.` - ); - expect(await invalid.isVisible()).toBeFalsy(); + await uptime.navigateToMonitorManagement(true); }); step('check add monitor button', async () => { @@ -48,20 +36,8 @@ journey( async ({ page, params }: { page: Page; params: any }) => { const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl }); - before(async () => { - await uptime.waitForLoadingToFinish(); - }); - step('Go to monitor-management', async () => { - await uptime.navigateToMonitorManagement(); - }); - - step('login to Kibana', async () => { - await uptime.loginToKibana('editor', 'changeme'); - const invalid = await page.locator( - `text=Username or password is incorrect. Please try again.` - ); - expect(await invalid.isVisible()).toBeFalsy(); + await uptime.navigateToMonitorManagement(true); }); step('check add monitor button', async () => { diff --git a/x-pack/plugins/synthetics/e2e/journeys/monitor_name.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/monitor_name.journey.ts index 9b1f9c2d9b938..896ef219f204c 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/monitor_name.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/monitor_name.journey.ts @@ -5,7 +5,7 @@ * 2.0. */ import uuid from 'uuid'; -import { journey, step, expect, before, Page } from '@elastic/synthetics'; +import { journey, step, expect, Page } from '@elastic/synthetics'; import { byTestId } from '@kbn/observability-plugin/e2e/utils'; import { monitorManagementPageProvider } from '../page_objects/monitor_management'; @@ -22,18 +22,8 @@ journey(`MonitorName`, async ({ page, params }: { page: Page; params: any }) => }); }; - before(async () => { - await uptime.waitForLoadingToFinish(); - }); - step('Go to monitor-management', async () => { - await uptime.navigateToMonitorManagement(); - }); - - step('login to Kibana', async () => { - await uptime.loginToKibana(); - const invalid = await page.locator(`text=Username or password is incorrect. Please try again.`); - expect(await invalid.isVisible()).toBeFalsy(); + await uptime.navigateToMonitorManagement(true); }); step('create basic monitor', async () => { diff --git a/x-pack/plugins/synthetics/e2e/journeys/read_only_user/monitor_management.ts b/x-pack/plugins/synthetics/e2e/journeys/read_only_user/monitor_management.ts index ea32b471019d9..96746ba56a96c 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/read_only_user/monitor_management.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/read_only_user/monitor_management.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { before, expect, journey, Page, step } from '@elastic/synthetics'; +import { expect, journey, Page, step } from '@elastic/synthetics'; import { byTestId } from '@kbn/observability-plugin/e2e/utils'; import { monitorManagementPageProvider } from '../../page_objects/monitor_management'; @@ -14,12 +14,8 @@ journey( async ({ page, params }: { page: Page; params: any }) => { const uptime = monitorManagementPageProvider({ page, kibanaUrl: params.kibanaUrl }); - before(async () => { - await uptime.waitForLoadingToFinish(); - }); - step('Go to monitor-management', async () => { - await uptime.navigateToMonitorManagement(); + await uptime.navigateToMonitorManagement(false); }); step('login to Kibana', async () => { diff --git a/x-pack/plugins/synthetics/e2e/journeys/step_duration.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/step_duration.journey.ts index ba62da5d4a4d7..8883ca6e47a30 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/step_duration.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/step_duration.journey.ts @@ -5,15 +5,11 @@ * 2.0. */ -import { journey, step, expect, before } from '@elastic/synthetics'; -import { waitForLoadingToFinish } from '@kbn/observability-plugin/e2e/utils'; +import { journey, step, expect } from '@elastic/synthetics'; import { loginPageProvider } from '../page_objects/login'; journey('StepsDuration', async ({ page, params }) => { const login = loginPageProvider({ page }); - before(async () => { - await waitForLoadingToFinish({ page }); - }); const queryParams = new URLSearchParams({ dateRangeStart: '2021-11-21T22:06:06.502Z', diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/add_monitor.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/add_monitor.journey.ts index 41251d819e616..79ab4fb3378fc 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/add_monitor.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/add_monitor.journey.ts @@ -144,24 +144,15 @@ const createMonitorJourney = ({ monitorEditDetails: Array<[string, string]>; }) => { journey( - `Synthetics - add monitor - ${monitorName}`, + `SyntheticsAddMonitor - ${monitorName}`, async ({ page, params }: { page: Page; params: any }) => { const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl }); step('Go to monitor management', async () => { - await syntheticsApp.navigateToMonitorManagement(); + await syntheticsApp.navigateToMonitorManagement(true); }); - step('login to Kibana', async () => { - await syntheticsApp.loginToKibana(); - const invalid = await page.locator( - `text=Username or password is incorrect. Please try again.` - ); - expect(await invalid.isVisible()).toBeFalsy(); - }); - - step('Ensure all montiors are deleted', async () => { - await syntheticsApp.navigateToMonitorManagement(); + step('Ensure all monitors are deleted', async () => { await syntheticsApp.waitForLoadingToFinish(); const isSuccessful = await syntheticsApp.deleteMonitors(); expect(isSuccessful).toBeTruthy(); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts index 2ba875bc1f42c..4bc328327e818 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts @@ -12,3 +12,4 @@ export * from './management_list.journey'; export * from './overview_sorting.journey'; export * from './overview_scrolling.journey'; export * from './overview_search.journey'; +export * from './private_locations.journey'; diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_scrolling.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_scrolling.journey.ts index 5bc3622aca113..ca04d3d3e074d 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_scrolling.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_scrolling.journey.ts @@ -20,9 +20,11 @@ journey('Overview Scrolling', async ({ page, params }) => { await enableMonitorManagedViaApi(params.kibanaUrl); await cleanTestMonitors(params); + const allPromises = []; for (let i = 0; i < 100; i++) { - await addTestMonitor(params.kibanaUrl, `test monitor ${i}`); + allPromises.push(addTestMonitor(params.kibanaUrl, `test monitor ${i}`)); } + await Promise.all(allPromises); await syntheticsApp.waitForLoadingToFinish(); }); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_sorting.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_sorting.journey.ts index 93aa36d725b89..85d1aae2f8421 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_sorting.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/overview_sorting.journey.ts @@ -13,7 +13,7 @@ import { } from './services/add_monitor'; import { syntheticsAppPageProvider } from '../../page_objects/synthetics_app'; -journey('Overview Sorting', async ({ page, params }) => { +journey('OverviewSorting', async ({ page, params }) => { const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl }); const testMonitor1 = 'acb'; // second alpha, first created const testMonitor2 = 'aCd'; // third alpha, second created @@ -26,26 +26,16 @@ journey('Overview Sorting', async ({ page, params }) => { await addTestMonitor(params.kibanaUrl, testMonitor1); await addTestMonitor(params.kibanaUrl, testMonitor2); await addTestMonitor(params.kibanaUrl, testMonitor3); - - await syntheticsApp.waitForLoadingToFinish(); }); step('Go to monitor-management', async () => { - await syntheticsApp.navigateToOverview(); - }); - - step('login to Kibana', async () => { - await syntheticsApp.loginToKibana(); - const invalid = await page.locator(`text=Username or password is incorrect. Please try again.`); - expect(await invalid.isVisible()).toBeFalsy(); + await syntheticsApp.navigateToOverview(true); }); - step('sort alpbhaetical asc', async () => { - await syntheticsApp.navigateToOverview(); + step('sort alphabetical asc', async () => { await page.waitForSelector(`[data-test-subj="syntheticsOverviewGridItem"]`); await page.click('[data-test-subj="syntheticsOverviewSortButton"]'); await page.click('button:has-text("Alphabetical")'); - await page.waitForSelector('text=Loading'); await page.waitForSelector(`text=${testMonitor1}`); await page.waitForSelector(`text=${testMonitor2}`); await page.waitForSelector(`text=${testMonitor3}`); @@ -61,7 +51,7 @@ journey('Overview Sorting', async ({ page, params }) => { expect(await correctThirdMonitor.count()).toBe(1); }); - step('sort alpbhaetical desc', async () => { + step('sort alphabetical desc', async () => { await page.waitForSelector(`[data-test-subj="syntheticsOverviewGridItem"]`); await page.click('[data-test-subj="syntheticsOverviewSortButton"]'); await page.click('button:has-text("Z -> A")'); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/private_locations.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/private_locations.journey.ts new file mode 100644 index 0000000000000..dccc927f62fda --- /dev/null +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/private_locations.journey.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 { journey, step, before, after, expect } from '@elastic/synthetics'; +import { byTestId } from '@kbn/observability-plugin/e2e/utils'; +import { waitForLoadingToFinish } from '@kbn/ux-plugin/e2e/journeys/utils'; +import { + addTestMonitor, + cleanPrivateLocations, + cleanTestMonitors, + getPrivateLocations, +} from './services/add_monitor'; +import { syntheticsAppPageProvider } from '../../page_objects/synthetics_app'; + +journey(`PrivateLocationsSettings`, async ({ page, params }) => { + const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl }); + + page.setDefaultTimeout(2 * 30000); + + before(async () => { + await cleanPrivateLocations(params); + await cleanTestMonitors(params); + }); + + after(async () => { + await cleanPrivateLocations(params); + await cleanTestMonitors(params); + }); + + step('Go to Settings page', async () => { + await syntheticsApp.navigateToSettings(true); + }); + + step('go to private locations tab', async () => { + await page.click('text=Private Locations'); + }); + + step('Click text=Private Locations', async () => { + await page.click('text=Private Locations'); + expect(page.url()).toBe('http://localhost:5620/app/synthetics/settings/private-locations'); + await page.click('text=No agent policies found'); + await page.click('text=Create agent policy'); + expect(page.url()).toBe('http://localhost:5620/app/fleet/policies?create'); + await page.click('[placeholder="Choose a name"]'); + await page.fill('[placeholder="Choose a name"]', 'Test fleet policy'); + await page.click('text=Collect system logs and metrics'); + await page.click('div[role="dialog"] button:has-text("Create agent policy")'); + await page.waitForTimeout(5 * 1000); + await waitForLoadingToFinish({ page }); + }); + step('Go to http://localhost:5620/app/fleet/policies', async () => { + await syntheticsApp.navigateToSettings(false); + await page.click('text=Private Locations'); + }); + step('Click button:has-text("Create location")', async () => { + await page.click('button:has-text("Create location")'); + await page.click('[aria-label="Location name"]'); + await page.fill('[aria-label="Location name"]', 'Test private'); + await page.press('[aria-label="Location name"]', 'Tab'); + await page.click('[aria-label="Select agent policy"]'); + await page.click('button[role="option"]:has-text("Test fleet policyAgents: 0")'); + await page.click('.euiComboBox__inputWrap'); + await page.fill('[aria-label="Tags"]', 'Basement'); + await page.press('[aria-label="Tags"]', 'Enter'); + await page.fill('[aria-label="Tags"]', 'Area51'); + await page.press('[aria-label="Tags"]', 'Enter'); + await page.click('button:has-text("Save")'); + }); + let locationId: string; + step('Click text=AlertingPrivate LocationsData Retention', async () => { + await page.click('text=AlertingPrivate LocationsData Retention'); + await page.click('h1:has-text("Settings")'); + + const privateLocations = await getPrivateLocations(params); + + const locations = privateLocations.attributes.locations; + + expect(locations.length).toBe(1); + + locationId = locations[0].id; + + await addTestMonitor(params.kibanaUrl, 'test-monitor', { + locations: [locations[0]], + type: 'browser', + }); + }); + + step('Click text=1', async () => { + await page.click('text=1'); + await page.click('text=Test private'); + await page.click('.euiTableCellContent__hoverItem .euiToolTipAnchor'); + await page.click('button:has-text("Tags")'); + await page.click('[aria-label="Tags"] >> text=Area51'); + await page.click( + 'main div:has-text("Private locations allow you to run monitors from your own premises. They require")' + ); + await page.click('text=Test private'); + + await page.click('.euiTableCellContent__hoverItem .euiToolTipAnchor'); + + await page.locator(byTestId(`deleteLocation-${locationId}`)).isDisabled(); + + const text = + 'This location cannot be deleted, because it has 1 monitors running. Please remove this location from your monitors before deleting this location.'; + + await page.locator(`text=${text}`).isVisible(); + }); + + step('Delete location', async () => { + await cleanTestMonitors(params); + + await page.click('text=Data Retention'); + expect(page.url()).toBe('http://localhost:5620/app/synthetics/settings/data-retention'); + await page.click('text=Private Locations'); + expect(page.url()).toBe('http://localhost:5620/app/synthetics/settings/private-locations'); + await page.click('[aria-label="Delete location"]'); + await page.click('button:has-text("Delete location")'); + await page.click('text=Create your first private location'); + }); +}); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts index 60471187a89e5..44985a4870e95 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor.ts @@ -6,6 +6,10 @@ */ import axios from 'axios'; +import { + privateLocationsSavedObjectId, + privateLocationsSavedObjectName, +} from '../../../../common/saved_objects/private_locations'; export const enableMonitorManagedViaApi = async (kibanaUrl: string) => { try { @@ -25,10 +29,10 @@ export const addTestMonitor = async ( params: Record = { type: 'browser' } ) => { const testData = { + locations: [{ id: 'us_central', isServiceManaged: true }], ...(params?.type !== 'browser' ? {} : data), ...(params || {}), name, - locations: [{ id: 'us_central', isServiceManaged: true }], }; try { await axios.post(kibanaUrl + '/internal/uptime/service/monitors', testData, { @@ -41,6 +45,21 @@ export const addTestMonitor = async ( } }; +export const getPrivateLocations = async (params: Record) => { + const getService = params.getService; + const server = getService('kibanaServer'); + + try { + return await server.savedObjects.get({ + id: privateLocationsSavedObjectId, + type: privateLocationsSavedObjectName, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.log(e); + } +}; + export const cleanTestMonitors = async (params: Record) => { const getService = params.getService; const server = getService('kibanaServer'); @@ -53,6 +72,21 @@ export const cleanTestMonitors = async (params: Record) => { } }; +export const cleanPrivateLocations = async (params: Record) => { + const getService = params.getService; + const server = getService('kibanaServer'); + + try { + await server.savedObjects.clean({ types: [privateLocationsSavedObjectName] }); + await server.savedObjects.clean({ + types: ['ingest-agent-policies', 'ingest-package-policies'], + }); + } catch (e) { + // eslint-disable-next-line no-console + console.log(e); + } +}; + const data = { type: 'browser', form_monitor_type: 'single', diff --git a/x-pack/plugins/synthetics/e2e/page_objects/login.tsx b/x-pack/plugins/synthetics/e2e/page_objects/login.tsx index ee0fe0f596b98..74839f9001a00 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/login.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/login.tsx @@ -5,6 +5,7 @@ * 2.0. */ import { Page } from '@elastic/synthetics'; +import { waitForLoadingToFinish } from '@kbn/ux-plugin/e2e/journeys/utils'; export function loginPageProvider({ page, @@ -19,12 +20,18 @@ export function loginPageProvider({ }) { return { async waitForLoadingToFinish() { - while (true) { - if ((await page.$('[data-test-subj=kbnLoadingMessage]')) === null) break; - await page.waitForTimeout(5 * 1000); - } + await waitForLoadingToFinish({ page }); }, async loginToKibana(usernameT?: 'editor' | 'viewer', passwordT?: string) { + try { + // Close Monitor Management tour added in 8.2.0 + await page.addInitScript(() => { + window.localStorage.setItem('xpack.synthetics.monitorManagement.openTour', 'false'); + }); + } catch (e) { + // ignore + } + if (isRemote) { await page.click('text="Log in with Elasticsearch"'); } @@ -35,13 +42,15 @@ export function loginPageProvider({ await page.click('[data-test-subj=loginSubmit]'); - await this.waitForLoadingToFinish(); - // Close Monitor Management tour added in 8.2.0 try { - await page.click('[data-test-subj=syntheticsManagementTourDismiss]'); + while (await page.isVisible('[data-test-subj=loginSubmit]')) { + await page.waitForTimeout(1000); + } } catch (e) { - return; + // ignore } + + await waitForLoadingToFinish({ page }); }, }; } diff --git a/x-pack/plugins/synthetics/e2e/page_objects/monitor_management.tsx b/x-pack/plugins/synthetics/e2e/page_objects/monitor_management.tsx index 3791aa8068176..540168e9917b7 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/monitor_management.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/monitor_management.tsx @@ -34,10 +34,13 @@ export function monitorManagementPageProvider({ }), ...utilsPageProvider({ page }), - async navigateToMonitorManagement() { + async navigateToMonitorManagement(doLogin = false) { await page.goto(monitorManagement, { waitUntil: 'networkidle', }); + if (doLogin) { + await this.loginToKibana(); + } await this.waitForMonitorManagementLoadingToFinish(); }, diff --git a/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx b/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx index 944919732b458..be5a503e3a277 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/synthetics_app.tsx @@ -22,6 +22,8 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib const monitorManagement = `${basePath}/app/synthetics/monitors`; const addMonitor = `${basePath}/app/synthetics/add-monitor`; const overview = `${basePath}/app/synthetics`; + const settingsPage = `${basePath}/app/synthetics/settings`; + return { ...loginPageProvider({ page, @@ -31,15 +33,21 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib }), ...utilsPageProvider({ page }), - async navigateToMonitorManagement() { + async navigateToMonitorManagement(doLogin = false) { await page.goto(monitorManagement, { waitUntil: 'networkidle', }); + if (doLogin) { + await this.loginToKibana(); + } await this.waitForMonitorManagementLoadingToFinish(); }, - async navigateToOverview() { + async navigateToOverview(doLogin = false) { await page.goto(overview, { waitUntil: 'networkidle' }); + if (doLogin) { + await this.loginToKibana(); + } }, async waitForMonitorManagementLoadingToFinish() { @@ -53,8 +61,19 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib return await this.findByText('Create monitor'); }, + async navigateToSettings(doLogin = true) { + await page.goto(settingsPage, { + waitUntil: 'networkidle', + }); + if (doLogin) { + await this.loginToKibana(); + } + }, + async navigateToAddMonitor() { - await page.goto(addMonitor); + await page.goto(addMonitor, { + waitUntil: 'networkidle', + }); }, async ensureIsOnMonitorConfigPage() { diff --git a/x-pack/plugins/synthetics/e2e/page_objects/utils.tsx b/x-pack/plugins/synthetics/e2e/page_objects/utils.tsx index 74fa8f6c8ce54..b9137632be266 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/utils.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/utils.tsx @@ -5,6 +5,7 @@ * 2.0. */ import { expect, Page } from '@elastic/synthetics'; +import { waitForLoadingToFinish } from '@kbn/ux-plugin/e2e/journeys/utils'; export function utilsPageProvider({ page }: { page: Page }) { return { @@ -13,10 +14,7 @@ export function utilsPageProvider({ page }: { page: Page }) { }, async waitForLoadingToFinish() { - while (true) { - if ((await page.$(this.byTestId('kbnLoadingMessage'))) === null) break; - await page.waitForTimeout(5 * 1000); - } + await waitForLoadingToFinish({ page }); }, async assertText({ text }: { text: string }) { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/table_title.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/table_title.tsx new file mode 100644 index 0000000000000..9378032ae30c8 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/table_title.tsx @@ -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 React from 'react'; +import { EuiText, EuiSpacer, EuiHorizontalRule } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export const TableTitle = ({ + pageIndex, + pageSize, + total, + label, +}: { + pageIndex: number; + pageSize: number; + total: number; + label: string; +}) => { + const start = pageIndex * pageSize + 1; + const end = Math.min(start + pageSize - 1, total); + return ( + <> + + + {start}-{end} + + ), + total, + label: {label}, + }} + /> + + + + + ); +}; 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 c7a3e6c9e6f78..9e0e22ed28457 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 @@ -245,7 +245,7 @@ function LocationSelect({ ); } -function LoadingState() { +export function LoadingState() { return ( diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/components/tags_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/components/tags_field.tsx new file mode 100644 index 0000000000000..8f82d855342ce --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/components/tags_field.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiComboBox, EuiFormRow } from '@elastic/eui'; +import { Control, Controller, FieldErrors } from 'react-hook-form'; +import { i18n } from '@kbn/i18n'; +import { PrivateLocation } from '../../../../../../common/runtime_types'; + +export function TagsField({ + tagsList, + control, + errors, +}: { + tagsList: string[]; + errors: FieldErrors; + control: Control; +}) { + return ( + + ( + ({ label: tag, value: tag })) ?? []} + options={tagsList.map((tag) => ({ label: tag, value: tag }))} + onCreateOption={(newTag) => { + field.onChange([...(field.value ?? []), newTag]); + }} + {...field} + onChange={(selectedTags) => { + field.onChange(selectedTags.map((tag) => tag.value)); + }} + /> + )} + /> + + ); +} +export const TAGS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.paramForm.tagsLabel', { + defaultMessage: 'Tags', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/page_header.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/page_header.tsx index bfae6a320e50c..117133e312fb3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/page_header.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/page_header.tsx @@ -10,12 +10,14 @@ import { EuiPageHeaderProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SYNTHETICS_SETTINGS_ROUTE } from '../../../../../common/constants'; +export type SettingsTabId = 'data-retention' | 'params' | 'alerting' | 'private-locations'; + export const getSettingsPageHeader = ( history: ReturnType, syntheticsPath: string ): EuiPageHeaderProps => { // Not a component, but it doesn't matter. Hooks are just functions - const match = useRouteMatch<{ tabId: string }>(SYNTHETICS_SETTINGS_ROUTE); // eslint-disable-line react-hooks/rules-of-hooks + const match = useRouteMatch<{ tabId: SettingsTabId }>(SYNTHETICS_SETTINGS_ROUTE); // eslint-disable-line react-hooks/rules-of-hooks if (!match) { return {}; @@ -23,6 +25,10 @@ export const getSettingsPageHeader = ( const { tabId } = match.params; + const replaceTab = (newTabId: SettingsTabId) => { + return `${syntheticsPath}${SYNTHETICS_SETTINGS_ROUTE.replace(':tabId', newTabId)}`; + }; + return { pageTitle: i18n.translate('xpack.synthetics.settingsRoute.pageHeaderTitle', { defaultMessage: 'Settings', @@ -33,15 +39,22 @@ export const getSettingsPageHeader = ( label: i18n.translate('xpack.synthetics.settingsTabs.alerting', { defaultMessage: 'Alerting', }), - isSelected: tabId === 'alerting' || !tabId, - href: `${syntheticsPath}${SYNTHETICS_SETTINGS_ROUTE.replace(':tabId', 'alerting')}`, + isSelected: tabId === 'alerting', + href: replaceTab('alerting'), + }, + { + label: i18n.translate('xpack.synthetics.settingsTabs.privateLocations', { + defaultMessage: 'Private Locations', + }), + isSelected: tabId === 'private-locations', + href: replaceTab('private-locations'), }, { label: i18n.translate('xpack.synthetics.settingsTabs.dataRetention', { - defaultMessage: 'Data retention', + defaultMessage: 'Data Retention', }), isSelected: tabId === 'data-retention', - href: `${syntheticsPath}${SYNTHETICS_SETTINGS_ROUTE.replace(':tabId', 'data-retention')}`, + href: replaceTab('data-retention'), }, ], }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx new file mode 100644 index 0000000000000..a7f3e51b7e3a5 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/add_location_flyout.tsx @@ -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 React from 'react'; +import { FormProvider } from 'react-hook-form'; +import { + EuiButtonEmpty, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiTitle, + EuiFlyout, + EuiButton, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { usePrivateLocationPermissions } from './hooks/use_private_location_permission'; +import { useFormWrapped } from '../../../../../hooks/use_form_wrapped'; +import { PrivateLocation } from '../../../../../../common/runtime_types'; +import { LocationForm } from './location_form'; +import { NEED_FLEET_READ_AGENT_POLICIES_PERMISSION, NEED_PERMISSIONS } from './translations'; + +export const AddLocationFlyout = ({ + onSubmit, + setIsOpen, + privateLocations, + isLoading, +}: { + isLoading: boolean; + onSubmit: (val: PrivateLocation) => void; + setIsOpen: (val: boolean) => void; + privateLocations: PrivateLocation[]; +}) => { + const form = useFormWrapped({ + mode: 'onSubmit', + reValidateMode: 'onChange', + shouldFocusError: true, + defaultValues: { + label: '', + agentPolicyId: '', + id: '', + geo: { + lat: 0, + lon: 0, + }, + concurrentMonitors: 1, + }, + }); + + const { handleSubmit } = form; + + const { canReadAgentPolicies } = usePrivateLocationPermissions(); + + const closeFlyout = () => { + setIsOpen(false); + }; + + return ( + + + + +

{ADD_PRIVATE_LOCATION}

+
+
+ + {!canReadAgentPolicies && ( + +

{NEED_FLEET_READ_AGENT_POLICIES_PERMISSION}

+
+ )} + +
+ + + + + {CANCEL_LABEL} + + + + + {SAVE_LABEL} + + + + +
+
+ ); +}; + +const ADD_PRIVATE_LOCATION = i18n.translate( + 'xpack.synthetics.monitorManagement.createPrivateLocations', + { + defaultMessage: 'Create private location', + } +); + +const CANCEL_LABEL = i18n.translate('xpack.synthetics.monitorManagement.cancelLabel', { + defaultMessage: 'Cancel', +}); + +const SAVE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.saveLabel', { + defaultMessage: 'Save', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx new file mode 100644 index 0000000000000..42aa5e8208ece --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/agent_policy_needed.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiLink, EuiButton, EuiEmptyPrompt, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useSyntheticsSettingsContext } from '../../../contexts'; +import { LEARN_MORE, READ_DOCS } from './empty_locations'; + +export const AgentPolicyNeeded = () => { + const { basePath } = useSyntheticsSettingsContext(); + + return ( + {AGENT_POLICY_NEEDED}} + body={

{ADD_AGENT_POLICY_DESCRIPTION}

} + actions={ + + {CREATE_AGENT_POLICY} + + } + footer={ + <> + +

{LEARN_MORE}

+
+ + {READ_DOCS} + + + } + /> + ); +}; + +const CREATE_AGENT_POLICY = i18n.translate('xpack.synthetics.monitorManagement.createAgentPolicy', { + defaultMessage: 'Create agent policy', +}); + +const AGENT_POLICY_NEEDED = i18n.translate('xpack.synthetics.monitorManagement.agentPolicyNeeded', { + defaultMessage: 'No agent policies found', +}); +const ADD_AGENT_POLICY_DESCRIPTION = i18n.translate( + 'xpack.synthetics.monitorManagement.addAgentPolicyDesc', + { + defaultMessage: + 'Private locations require an Agent policy. In order to add a private location, first you must create an Agent policy in Fleet.', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.tsx new file mode 100644 index 0000000000000..782164b8db8fd --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/delete_location.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 { EuiButtonIcon, EuiConfirmModal, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useSyntheticsSettingsContext } from '../../../contexts'; + +export const DeleteLocation = ({ + loading, + id, + label, + locationMonitors, + onDelete, +}: { + id: string; + label: string; + loading?: boolean; + onDelete: (id: string) => void; + locationMonitors: Array<{ id: string; count: number }>; +}) => { + const monCount = locationMonitors?.find((l) => l.id === id)?.count ?? 0; + const canDelete = monCount === 0; + + const { canSave } = useSyntheticsSettingsContext(); + + const [isModalOpen, setIsModalOpen] = useState(false); + + const deleteModal = ( + setIsModalOpen(false)} + onConfirm={() => onDelete(id)} + cancelButtonText={CANCEL_LABEL} + confirmButtonText={CONFIRM_LABEL} + buttonColor="danger" + defaultFocusedButton="confirm" + isLoading={loading} + > +

{ARE_YOU_SURE_LABEL}

+
+ ); + + return ( + <> + {isModalOpen && deleteModal} + + { + setIsModalOpen(true); + }} + isDisabled={!canDelete || !canSave} + /> + + + ); +}; + +const DELETE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.deleteLocation', { + defaultMessage: 'Delete location', +}); + +const CONFIRM_LABEL = i18n.translate('xpack.synthetics.monitorManagement.deleteLocationLabel', { + defaultMessage: 'Delete location', +}); + +const CANCEL_LABEL = i18n.translate('xpack.synthetics.monitorManagement.cancelLabel', { + defaultMessage: 'Cancel', +}); + +const ARE_YOU_SURE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.areYouSure', { + defaultMessage: 'Are you sure you want to delete this location?', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx new file mode 100644 index 0000000000000..9bee7b8c2e222 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/empty_locations.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiEmptyPrompt, EuiButton, EuiLink, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useDispatch } from 'react-redux'; +import { setAddingNewPrivateLocation, setManageFlyoutOpen } from '../../../state/private_locations'; + +export const EmptyLocations = ({ + inFlyout = true, + setIsAddingNew, + disabled, +}: { + inFlyout?: boolean; + disabled?: boolean; + setIsAddingNew?: (val: boolean) => void; +}) => { + const dispatch = useDispatch(); + + return ( + {ADD_FIRST_LOCATION}} + titleSize="s" + body={ + + {!inFlyout ? FIRST_MONITOR : ''} {START_ADDING_LOCATIONS_DESCRIPTION} + + } + actions={ + { + setIsAddingNew?.(true); + dispatch(setManageFlyoutOpen(true)); + dispatch(setAddingNewPrivateLocation(true)); + }} + > + {ADD_LOCATION} + + } + footer={ + + {LEARN_MORE} + + } + /> + ); +}; + +export const PrivateLocationDocsLink = ({ label }: { label?: string }) => ( + + {label ?? READ_DOCS} + +); + +const FIRST_MONITOR = i18n.translate('xpack.synthetics.monitorManagement.firstLocationMonitor', { + defaultMessage: 'In order to create a monitor, you will need to add a location first.', +}); + +const ADD_FIRST_LOCATION = i18n.translate( + 'xpack.synthetics.monitorManagement.createFirstLocation', + { + defaultMessage: 'Create your first private location', + } +); + +export const START_ADDING_LOCATIONS_DESCRIPTION = i18n.translate( + 'xpack.synthetics.monitorManagement.startAddingLocationsDescription', + { + defaultMessage: + 'Private locations allow you to run monitors from your own premises. They require an Elastic agent and Agent policy which you can control and maintain via Fleet.', + } +); + +const ADD_LOCATION = i18n.translate('xpack.synthetics.monitorManagement.createLocation', { + defaultMessage: 'Create location', +}); + +export const READ_DOCS = i18n.translate('xpack.synthetics.monitorManagement.readDocs', { + defaultMessage: 'read the docs', +}); + +export const LEARN_MORE = i18n.translate('xpack.synthetics.monitorManagement.learnMore', { + defaultMessage: 'For more information,', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.test.tsx new file mode 100644 index 0000000000000..c70150b6c2855 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.test.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { defaultCore, WrappedHelper } from '../../../../utils/testing'; + +import { useLocationMonitors } from './use_location_monitors'; + +describe('useLocationMonitors', () => { + it('returns expected results', () => { + const { result } = renderHook(() => useLocationMonitors(), { wrapper: WrappedHelper }); + + expect(result.current).toStrictEqual({ locationMonitors: [], loading: true }); + expect(defaultCore.savedObjects.client.find).toHaveBeenCalledWith({ + aggs: { + locations: { + terms: { field: 'synthetics-monitor.attributes.locations.id', size: 10000 }, + }, + }, + perPage: 0, + type: 'synthetics-monitor', + }); + }); + + it('returns expected results after data', async () => { + defaultCore.savedObjects.client.find = jest.fn().mockReturnValue({ + aggregations: { + locations: { + buckets: [ + { key: 'Test', doc_count: 5 }, + { key: 'Test 1', doc_count: 0 }, + ], + }, + }, + }); + + const { result, waitForNextUpdate } = renderHook(() => useLocationMonitors(), { + wrapper: WrappedHelper, + }); + + expect(result.current).toStrictEqual({ locationMonitors: [], loading: true }); + + await waitForNextUpdate(); + + expect(result.current).toStrictEqual({ + loading: false, + locationMonitors: [ + { + id: 'Test', + count: 5, + }, + { + id: 'Test 1', + count: 0, + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts new file mode 100644 index 0000000000000..6ea682d3db7a0 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.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 { useMemo } from 'react'; +import { useFetcher } from '@kbn/observability-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { + monitorAttributes, + syntheticsMonitorType, +} from '../../../../../../../common/types/saved_objects'; + +interface AggsResponse { + locations: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; +} + +export const useLocationMonitors = () => { + const { savedObjects } = useKibana().services; + + const { data, loading } = useFetcher(() => { + const aggs = { + locations: { + terms: { + field: `${monitorAttributes}.locations.id`, + size: 10000, + }, + }, + }; + return savedObjects?.client.find({ + type: syntheticsMonitorType, + perPage: 0, + aggs, + }); + }, []); + + return useMemo(() => { + if (data?.aggregations) { + const newValues = (data.aggregations as AggsResponse)?.locations.buckets.map( + ({ key, doc_count: count }) => ({ id: key, count }) + ); + + return { locationMonitors: newValues, loading }; + } + return { locationMonitors: [], loading }; + }, [data, loading]); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx new file mode 100644 index 0000000000000..e343e9faf1aec --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.test.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { defaultCore, WrappedHelper } from '../../../../utils/testing'; + +import { useLocationsAPI } from './use_locations_api'; + +describe('useLocationsAPI', () => { + it('returns expected results', () => { + const { result } = renderHook(() => useLocationsAPI(), { + wrapper: WrappedHelper, + }); + + expect(result.current).toEqual( + expect.objectContaining({ + loading: true, + privateLocations: [], + }) + ); + expect(defaultCore.savedObjects.client.get).toHaveBeenCalledWith( + 'synthetics-privates-locations', + 'synthetics-privates-locations-singleton' + ); + }); + defaultCore.savedObjects.client.get = jest.fn().mockReturnValue({ + attributes: { + locations: [ + { + id: 'Test', + agentPolicyId: 'testPolicy', + }, + ], + }, + }); + it('returns expected results after data', async () => { + const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI(), { + wrapper: WrappedHelper, + }); + + expect(result.current).toEqual( + expect.objectContaining({ + loading: true, + privateLocations: [], + }) + ); + + await waitForNextUpdate(); + + expect(result.current).toEqual( + expect.objectContaining({ + loading: false, + privateLocations: [ + { + id: 'Test', + agentPolicyId: 'testPolicy', + }, + ], + }) + ); + }); + + it('adds location on submit', async () => { + const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI(), { + wrapper: WrappedHelper, + }); + + await waitForNextUpdate(); + + result.current.onSubmit({ + id: 'new', + agentPolicyId: 'newPolicy', + label: 'new', + concurrentMonitors: 1, + geo: { + lat: 0, + lon: 0, + }, + }); + + await waitForNextUpdate(); + + expect(defaultCore.savedObjects.client.create).toHaveBeenCalledWith( + 'synthetics-privates-locations', + { + locations: [ + { id: 'Test', agentPolicyId: 'testPolicy' }, + { + concurrentMonitors: 1, + id: 'newPolicy', + geo: { + lat: 0, + lon: 0, + }, + label: 'new', + agentPolicyId: 'newPolicy', + }, + ], + }, + { id: 'synthetics-privates-locations-singleton', overwrite: true } + ); + }); + + it('deletes location on delete', async () => { + defaultCore.savedObjects.client.get = jest.fn().mockReturnValue({ + attributes: { + locations: [ + { + id: 'Test', + agentPolicyId: 'testPolicy', + }, + { + id: 'Test1', + agentPolicyId: 'testPolicy1', + }, + ], + }, + }); + + const { result, waitForNextUpdate } = renderHook(() => useLocationsAPI(), { + wrapper: WrappedHelper, + }); + + await waitForNextUpdate(); + + result.current.onDelete('Test'); + + await waitForNextUpdate(); + + expect(defaultCore.savedObjects.client.create).toHaveBeenLastCalledWith( + 'synthetics-privates-locations', + { + locations: [ + { + id: 'Test1', + agentPolicyId: 'testPolicy1', + }, + ], + }, + { id: 'synthetics-privates-locations-singleton', overwrite: true } + ); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts new file mode 100644 index 0000000000000..6b35e79152c87 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useFetcher } from '@kbn/observability-plugin/public'; +import { useState } from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useDispatch } from 'react-redux'; +import { setAddingNewPrivateLocation } from '../../../../state/private_locations'; +import { + getSyntheticsPrivateLocations, + setSyntheticsPrivateLocations, +} from '../../../../state/private_locations/api'; +import { PrivateLocation } from '../../../../../../../common/runtime_types'; + +export const useLocationsAPI = () => { + const [formData, setFormData] = useState(); + const [deleteId, setDeleteId] = useState(); + const [privateLocations, setPrivateLocations] = useState([]); + + const { savedObjects } = useKibana().services; + + const dispatch = useDispatch(); + + const setIsAddingNew = (val: boolean) => dispatch(setAddingNewPrivateLocation(val)); + + const { loading: fetchLoading } = useFetcher(async () => { + const result = await getSyntheticsPrivateLocations(savedObjects?.client!); + setPrivateLocations(result); + return result; + }, []); + + const { loading: saveLoading } = useFetcher(async () => { + if (privateLocations && formData) { + const existingLocations = privateLocations.filter((loc) => loc.id !== formData.agentPolicyId); + + const result = await setSyntheticsPrivateLocations(savedObjects?.client!, { + locations: [...(existingLocations ?? []), { ...formData, id: formData.agentPolicyId }], + }); + setPrivateLocations(result.locations); + setFormData(undefined); + setIsAddingNew(false); + return result; + } + }, [formData, privateLocations]); + + const onSubmit = (data: PrivateLocation) => { + setFormData(data); + }; + + const onDelete = (id: string) => { + setDeleteId(id); + }; + + const { loading: deleteLoading } = useFetcher(async () => { + if (deleteId) { + const result = await setSyntheticsPrivateLocations(savedObjects?.client!, { + locations: (privateLocations ?? []).filter((loc) => loc.id !== deleteId), + }); + setPrivateLocations(result.locations); + setDeleteId(undefined); + return result; + } + }, [deleteId, privateLocations]); + + return { + formData, + onSubmit, + onDelete, + deleteLoading: Boolean(deleteLoading), + loading: Boolean(fetchLoading || saveLoading), + privateLocations, + }; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_private_location_permission.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_private_location_permission.ts new file mode 100644 index 0000000000000..f62cc05f8fd0e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_private_location_permission.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 { useKibana } from '@kbn/kibana-react-plugin/public'; +import { BrowserFields, ConfigKey } from '../../../../../../../common/runtime_types'; +import { ClientPluginsStart } from '../../../../../../plugin'; + +export function usePrivateLocationPermissions(monitor?: BrowserFields) { + const { fleet } = useKibana().services; + + const canSaveIntegrations: boolean = Boolean(fleet?.authz.integrations.writeIntegrationPolicies); + const canReadAgentPolicies = Boolean(fleet?.authz.fleet.readAgentPolicies); + + const locations = (monitor as BrowserFields)?.[ConfigKey.LOCATIONS]; + + const hasPrivateLocation = locations?.some((location) => !location.isServiceManaged); + + const canUpdatePrivateMonitor = !(hasPrivateLocation && !canSaveIntegrations); + + return { canUpdatePrivateMonitor, canReadAgentPolicies }; +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx new file mode 100644 index 0000000000000..89fe466d241f1 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/location_form.tsx @@ -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 React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiFieldText, + EuiForm, + EuiFormRow, + EuiSpacer, + EuiCallOut, + EuiCode, + EuiLink, +} from '@elastic/eui'; +import { useSelector } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import { useFormContext, useFormState } from 'react-hook-form'; +import { TagsField } from '../components/tags_field'; +import { PrivateLocation } from '../../../../../../common/runtime_types'; +import { AgentPolicyNeeded } from './agent_policy_needed'; +import { PolicyHostsField } from './policy_hosts'; +import { selectAgentPolicies } from '../../../state/private_locations'; + +export const LocationForm = ({ + privateLocations, +}: { + onDiscard?: () => void; + privateLocations: PrivateLocation[]; +}) => { + const { data } = useSelector(selectAgentPolicies); + const { control, register } = useFormContext(); + const { errors } = useFormState(); + + const tagsList = privateLocations.reduce((acc, item) => { + const tags = item.tags || []; + return [...acc, ...tags]; + }, [] as string[]); + + return ( + <> + {data?.items.length === 0 && } + + + { + return privateLocations.some((loc) => loc.label === val) + ? NAME_ALREADY_EXISTS + : undefined; + }, + })} + /> + + + + + + + +

+ { + elastic-agent-complete, + link: ( + + + + ), + }} + /> + } +

+
+
+ + ); +}; + +export const AGENT_CALLOUT_TITLE = i18n.translate( + 'xpack.synthetics.monitorManagement.agentCallout.title', + { + defaultMessage: 'Requirement', + } +); + +export const LOCATION_NAME_LABEL = i18n.translate( + 'xpack.synthetics.monitorManagement.locationName', + { + defaultMessage: 'Location name', + } +); + +const NAME_ALREADY_EXISTS = i18n.translate('xpack.synthetics.monitorManagement.alreadyExists', { + defaultMessage: 'Location name already exists.', +}); + +const NAME_REQUIRED = i18n.translate('xpack.synthetics.monitorManagement.nameRequired', { + defaultMessage: 'Location name is required', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx new file mode 100644 index 0000000000000..86ddf2deffb52 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/locations_table.tsx @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { + EuiBadge, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useDispatch } from 'react-redux'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { Criteria } from '@elastic/eui/src/components/basic_table/basic_table'; +import { ViewLocationMonitors } from './view_location_monitors'; +import { TableTitle } from '../../common/components/table_title'; +import { TAGS_LABEL } from '../components/tags_field'; +import { useSyntheticsSettingsContext } from '../../../contexts'; +import { setAddingNewPrivateLocation } from '../../../state/private_locations'; +import { PrivateLocationDocsLink, START_ADDING_LOCATIONS_DESCRIPTION } from './empty_locations'; +import { PrivateLocation } from '../../../../../../common/runtime_types'; +import { DeleteLocation } from './delete_location'; +import { useLocationMonitors } from './hooks/use_location_monitors'; +import { PolicyName } from './policy_name'; +import { LOCATION_NAME_LABEL } from './location_form'; +import { ClientPluginsStart } from '../../../../../plugin'; + +interface ListItem extends PrivateLocation { + monitors: number; +} + +export const PrivateLocationsTable = ({ + deleteLoading, + onDelete, + privateLocations, +}: { + deleteLoading: boolean; + onDelete: (id: string) => void; + privateLocations: PrivateLocation[]; +}) => { + const dispatch = useDispatch(); + + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + + const { locationMonitors, loading } = useLocationMonitors(); + + const { canSave } = useSyntheticsSettingsContext(); + + const tagsList = privateLocations.reduce((acc, item) => { + const tags = item.tags || []; + return new Set([...acc, ...tags]); + }, new Set()); + + const columns = [ + { + field: 'label', + name: LOCATION_NAME_LABEL, + }, + { + field: 'monitors', + name: MONITORS, + render: (monitors: number, item: ListItem) => ( + + ), + }, + { + field: 'agentPolicyId', + name: AGENT_POLICY_LABEL, + render: (agentPolicyId: string) => , + }, + { + name: TAGS_LABEL, + field: 'tags', + sortable: true, + render: (val: string[]) => { + const tags = val ?? []; + if (tags.length === 0) { + return --; + } + return ( + + {tags.map((tag) => ( + + {tag} + + ))} + + ); + }, + }, + { + name: ACTIONS_LABEL, + actions: [ + { + name: DELETE_LOCATION, + description: DELETE_LOCATION, + render: (item: ListItem) => ( + + ), + isPrimary: true, + 'data-test-subj': 'action-delete', + }, + ], + }, + ]; + + const items = privateLocations.map((location) => ({ + ...location, + monitors: locationMonitors?.find((l) => l.id === location.id)?.count ?? 0, + })); + + const setIsAddingNew = (val: boolean) => dispatch(setAddingNewPrivateLocation(val)); + + const { fleet } = useKibana().services; + + const hasFleetPermissions = Boolean(fleet?.authz.fleet.readAgentPolicies); + + const renderToolRight = () => { + return [ + setIsAddingNew(true)} + iconType="plusInCircle" + > + {ADD_LABEL} + , + ]; + }; + + return ( +
+ + {START_ADDING_LOCATIONS_DESCRIPTION} + + + + itemId={'id'} + tableLayout="auto" + tableCaption={PRIVATE_LOCATIONS} + items={items} + columns={columns} + childrenBetween={ + + } + pagination={{ + pageSize, + pageIndex, + }} + onTableChange={({ page }: Criteria) => { + setPageIndex(page?.index ?? 0); + setPageSize(page?.size ?? 10); + }} + search={{ + toolsRight: renderToolRight(), + box: { + incremental: true, + }, + filters: [ + { + type: 'field_value_selection', + field: 'tags', + name: TAGS_LABEL, + multiSelect: true, + options: [...tagsList].map((tag) => ({ + value: tag, + name: tag, + view: tag, + })), + }, + ], + }} + /> +
+ ); +}; + +const PRIVATE_LOCATIONS = i18n.translate('xpack.synthetics.monitorManagement.privateLocations', { + defaultMessage: 'Private locations', +}); + +const ACTIONS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.actions', { + defaultMessage: 'Actions', +}); + +export const MONITORS = i18n.translate('xpack.synthetics.monitorManagement.monitors', { + defaultMessage: 'Monitors', +}); + +export const AGENT_POLICY_LABEL = i18n.translate('xpack.synthetics.monitorManagement.agentPolicy', { + defaultMessage: 'Agent Policy', +}); + +const DELETE_LOCATION = i18n.translate( + 'xpack.synthetics.settingsRoute.privateLocations.deleteLabel', + { + defaultMessage: 'Delete private location', + } +); + +const ADD_LABEL = i18n.translate('xpack.synthetics.monitorManagement.createLocation', { + defaultMessage: 'Create location', +}); + +export const LEARN_MORE = i18n.translate('xpack.synthetics.privateLocations.learnMore.label', { + defaultMessage: 'Learn more.', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx new file mode 100644 index 0000000000000..9cc313a106c96 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_empty_state.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; +import { useSelector } from 'react-redux'; +import { PrivateLocation } from '../../../../../../common/runtime_types'; +import { AgentPolicyNeeded } from './agent_policy_needed'; +import { EmptyLocations } from './empty_locations'; +import { selectAgentPolicies } from '../../../state/private_locations'; + +export const ManageEmptyState: FC<{ + privateLocations: PrivateLocation[]; + hasFleetPermissions: boolean; + setIsAddingNew: (val: boolean) => void; +}> = ({ children, privateLocations, setIsAddingNew, hasFleetPermissions }) => { + const { data: agentPolicies } = useSelector(selectAgentPolicies); + + if (agentPolicies?.total === 0) { + return ; + } + + if (privateLocations.length === 0) { + return ; + } + + return <>{children}; +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.tsx new file mode 100644 index 0000000000000..0aa5e181c5ac1 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/manage_private_locations.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, { useEffect } from 'react'; +import { EuiCallOut } from '@elastic/eui'; +import { useDispatch, useSelector } from 'react-redux'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { LoadingState } from '../../monitors_page/overview/overview/monitor_detail_flyout'; +import { PrivateLocationsTable } from './locations_table'; +import { ClientPluginsStart } from '../../../../../plugin'; +import { ManageEmptyState } from './manage_empty_state'; +import { AddLocationFlyout } from './add_location_flyout'; +import { useLocationsAPI } from './hooks/use_locations_api'; +import { + getAgentPoliciesAction, + selectAddingNewPrivateLocation, + setAddingNewPrivateLocation, +} from '../../../state/private_locations'; +import { PrivateLocation } from '../../../../../../common/runtime_types'; +import { getServiceLocations } from '../../../state'; +import { NEED_FLEET_READ_AGENT_POLICIES_PERMISSION, NEED_PERMISSIONS } from './translations'; + +export const ManagePrivateLocations = () => { + const dispatch = useDispatch(); + + const isAddingNew = useSelector(selectAddingNewPrivateLocation); + + const setIsAddingNew = (val: boolean) => dispatch(setAddingNewPrivateLocation(val)); + + const { onSubmit, loading, privateLocations, onDelete, deleteLoading } = useLocationsAPI(); + + const { fleet } = useKibana().services; + + const hasFleetPermissions = Boolean(fleet?.authz.fleet.readAgentPolicies); + + useEffect(() => { + dispatch(getAgentPoliciesAction.get()); + dispatch(getServiceLocations()); + }, [dispatch]); + + const handleSubmit = (formData: PrivateLocation) => { + onSubmit(formData); + }; + + return ( + <> + {loading ? ( + + ) : ( + + + + )} + {!hasFleetPermissions && ( + +

{NEED_FLEET_READ_AGENT_POLICIES_PERMISSION}

+
+ )} + {isAddingNew ? ( + + ) : null} + + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/policy_hosts.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/policy_hosts.tsx new file mode 100644 index 0000000000000..e91a8b529131c --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/policy_hosts.tsx @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { Controller, FieldErrors, Control } from 'react-hook-form'; +import { useSelector } from 'react-redux'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiHealth, + EuiSuperSelect, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { PrivateLocation } from '../../../../../../common/runtime_types'; +import { selectAgentPolicies } from '../../../state/private_locations'; + +export const PolicyHostsField = ({ + errors, + control, + privateLocations, +}: { + errors: FieldErrors; + control: Control; + privateLocations: PrivateLocation[]; +}) => { + const { data } = useSelector(selectAgentPolicies); + + const policyHostsOptions = data?.items.map((item) => { + const hasLocation = privateLocations.find((location) => location.agentPolicyId === item.id); + return { + disabled: Boolean(hasLocation), + value: item.id, + inputDisplay: ( + + {item.name} + + ), + 'data-test-subj': item.name, + dropdownDisplay: ( + + <> + + {item.name} + + + + +

+ {AGENTS_LABEL} {item.agents} +

+
+
+ + +

{item.description}

+
+
+
+ +
+ ), + }; + }); + + return ( + + ( + + )} + /> + + ); +}; + +const AGENTS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.agentsLabel', { + defaultMessage: 'Agents: ', +}); + +const SELECT_POLICY_HOSTS = i18n.translate('xpack.synthetics.monitorManagement.selectPolicyHost', { + defaultMessage: 'Select agent policy', +}); + +const SELECT_POLICY_HOSTS_HELP_TEXT = i18n.translate( + 'xpack.synthetics.monitorManagement.selectPolicyHost.helpText', + { + defaultMessage: 'We recommend using a single Elastic agent per agent policy.', + } +); + +const POLICY_HOST_LABEL = i18n.translate('xpack.synthetics.monitorManagement.policyHost', { + defaultMessage: 'Agent policy', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/policy_name.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/policy_name.tsx new file mode 100644 index 0000000000000..ee17a5f095377 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/policy_name.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiLink, EuiLoadingSpinner, EuiText, EuiTextColor } from '@elastic/eui'; +import { useSelector } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import { useSyntheticsSettingsContext } from '../../../contexts'; +import { usePrivateLocationPermissions } from './hooks/use_private_location_permission'; +import { selectAgentPolicies } from '../../../state/private_locations'; + +export const PolicyName = ({ agentPolicyId }: { agentPolicyId: string }) => { + const { canReadAgentPolicies } = usePrivateLocationPermissions(); + + const { basePath } = useSyntheticsSettingsContext(); + + const { data: policies, loading } = useSelector(selectAgentPolicies); + + const policy = policies?.items.find((policyT) => policyT.id === agentPolicyId); + + if (loading) { + return ; + } + + return ( + +

+ {canReadAgentPolicies && ( + + {policy ? ( + + {policy?.name} + + ) : ( + + {POLICY_IS_DELETED} + + )} + + )} +

+
+ ); +}; + +const POLICY_IS_DELETED = i18n.translate('xpack.synthetics.monitorManagement.deletedPolicy', { + defaultMessage: 'Policy is deleted', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/translations.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/translations.ts new file mode 100644 index 0000000000000..f67305d739988 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/translations.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 { i18n } from '@kbn/i18n'; + +export const NEED_PERMISSIONS = i18n.translate( + 'xpack.synthetics.monitorManagement.needPermissions', + { + defaultMessage: 'Need permissions', + } +); + +export const NEED_FLEET_READ_AGENT_POLICIES_PERMISSION = i18n.translate( + 'xpack.synthetics.monitorManagement.needFleetReadAgentPoliciesPermission', + { + defaultMessage: + 'You are not authorized to access Fleet. Fleet permissions are required to create new private locations.', + } +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/view_location_monitors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/view_location_monitors.tsx new file mode 100644 index 0000000000000..0c52216dc37b5 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/view_location_monitors.tsx @@ -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 React, { useState } from 'react'; +import { EuiPopover, EuiButtonEmpty, EuiButton, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useHistory } from 'react-router-dom'; + +export const ViewLocationMonitors = ({ + count, + locationName, +}: { + count: number; + locationName: string; +}) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const onButtonClick = () => setIsPopoverOpen((prevState) => !prevState); + const closePopover = () => setIsPopoverOpen(false); + + const button = {count}; + + const history = useHistory(); + + return ( + + {locationName} }} + /> + + {count > 0 ? ( + + {VIEW_LOCATION_MONITORS} + + ) : ( + + {CREATE_MONITOR} + + )} + + ); +}; + +const VIEW_LOCATION_MONITORS = i18n.translate( + 'xpack.synthetics.monitorManagement.viewLocationMonitors', + { + defaultMessage: 'View location monitors', + } +); + +const CREATE_MONITOR = i18n.translate('xpack.synthetics.monitorManagement.createLocationMonitors', { + defaultMessage: 'Create monitor', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/settings_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/settings_page.tsx index 9caa9d34a4b57..ad6e3090d10b9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/settings_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/settings_page.tsx @@ -7,17 +7,28 @@ import React from 'react'; import { Redirect, useParams } from 'react-router-dom'; +import { SettingsTabId } from './page_header'; import { DataRetentionTab } from './data_retention'; import { useSettingsBreadcrumbs } from './use_settings_breadcrumbs'; +import { ManagePrivateLocations } from './private_locations/manage_private_locations'; export const SettingsPage = () => { useSettingsBreadcrumbs(); - const { tabId } = useParams<{ tabId: string }>(); + const { tabId } = useParams<{ tabId: SettingsTabId }>(); - if (!tabId) { - return ; - } + const renderTab = () => { + switch (tabId) { + case 'private-locations': + return ; + case 'data-retention': + return ; + case 'alerting': + return
TODO: Alerting
; + default: + return ; + } + }; - return
{tabId === 'alerting' ?
TODO: Alerting
: }
; + return
{renderTab()}
; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_settings_context.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_settings_context.tsx index 61231cfde6dfe..3ff9765a05cd6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_settings_context.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/contexts/synthetics_settings_context.tsx @@ -13,6 +13,7 @@ import { I18nStart, } from '@kbn/core/public'; import React, { createContext, useContext, useMemo } from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ClientPluginsSetup, ClientPluginsStart } from '../../../plugin'; import { CLIENT_DEFAULTS, CONTEXT_DEFAULTS } from '../../../../common/constants'; import { useGetUrlParams } from '../hooks'; @@ -43,6 +44,7 @@ export interface SyntheticsAppProps { } export interface SyntheticsSettingsContextValues { + canSave: boolean; basePath: string; dateRangeStart: string; dateRangeEnd: string; @@ -69,6 +71,7 @@ const defaultContext: SyntheticsSettingsContextValues = { isInfraAvailable: true, isLogsAvailable: true, isDev: false, + canSave: false, }; export const SyntheticsSettingsContext = createContext(defaultContext); @@ -81,8 +84,13 @@ export const SyntheticsSettingsContextProvider: React.FC = ( const { dateRangeStart, dateRangeEnd } = useGetUrlParams(); + const { application } = useKibana().services; + + const canSave = (application?.capabilities.uptime.save ?? false) as boolean; + const value = useMemo(() => { return { + canSave, isDev, basePath, isApmAvailable, @@ -93,6 +101,7 @@ export const SyntheticsSettingsContextProvider: React.FC = ( dateRangeEnd: dateRangeEnd ?? DATE_RANGE_END, }; }, [ + canSave, isDev, basePath, isApmAvailable, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/actions.ts new file mode 100644 index 0000000000000..60586a99c380e --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/actions.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createAction } from '@reduxjs/toolkit'; +import { createAsyncAction } from '../utils/actions'; +import { AgentPoliciesList } from '.'; + +export const getAgentPoliciesAction = createAsyncAction( + '[AGENT POLICIES] GET' +); + +export const setManageFlyoutOpen = createAction('SET MANAGE FLYOUT OPEN'); + +export const setAddingNewPrivateLocation = createAction('SET MANAGE FLYOUT ADDING NEW'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts new file mode 100644 index 0000000000000..3c48696c685b9 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.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 { SavedObjectsClientContract } from '@kbn/core/public'; +import { SyntheticsPrivateLocations } from '../../../../../common/runtime_types'; +import { apiService } from '../../../../utils/api_service/api_service'; +import { AgentPoliciesList } from '.'; +import { + privateLocationsSavedObjectId, + privateLocationsSavedObjectName, +} from '../../../../../common/saved_objects/private_locations'; + +const FLEET_URLS = { + AGENT_POLICIES: '/api/fleet/agent_policies', +}; + +export const fetchAgentPolicies = async (): Promise => { + return await apiService.get( + FLEET_URLS.AGENT_POLICIES, + { + page: 1, + perPage: 10000, + sortField: 'name', + sortOrder: 'asc', + full: true, + kuery: 'ingest-agent-policies.is_managed : false', + }, + null + ); +}; + +export const setSyntheticsPrivateLocations = async ( + client: SavedObjectsClientContract, + privateLocations: SyntheticsPrivateLocations +) => { + const result = await client.create(privateLocationsSavedObjectName, privateLocations, { + id: privateLocationsSavedObjectId, + overwrite: true, + }); + + return result.attributes; +}; + +export const getSyntheticsPrivateLocations = async (client: SavedObjectsClientContract) => { + try { + const obj = await client.get( + privateLocationsSavedObjectName, + privateLocationsSavedObjectId + ); + return obj?.attributes.locations ?? []; + } catch (getErr) { + return []; + } +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/effects.ts new file mode 100644 index 0000000000000..c8889585676b4 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/effects.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 { takeLeading } from 'redux-saga/effects'; +import { fetchEffectFactory } from '../utils/fetch_effect'; +import { fetchAgentPolicies } from './api'; +import { getAgentPoliciesAction } from './actions'; + +export function* fetchAgentPoliciesEffect() { + yield takeLeading( + getAgentPoliciesAction.get, + fetchEffectFactory( + fetchAgentPolicies, + getAgentPoliciesAction.success, + getAgentPoliciesAction.fail + ) + ); +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/index.ts new file mode 100644 index 0000000000000..5b023a8bd0b55 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/index.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 { createReducer } from '@reduxjs/toolkit'; +import { AgentPolicy } from '@kbn/fleet-plugin/common'; +import { IHttpSerializedFetchError } from '..'; +import { getAgentPoliciesAction, setAddingNewPrivateLocation } from './actions'; + +export interface AgentPoliciesList { + items: AgentPolicy[]; + total: number; + page: number; + perPage: number; +} + +export interface AgentPoliciesState { + data: AgentPoliciesList | null; + loading: boolean; + error: IHttpSerializedFetchError | null; + isManageFlyoutOpen?: boolean; + isAddingNewPrivateLocation?: boolean; +} + +const initialState: AgentPoliciesState = { + data: null, + loading: false, + error: null, + isManageFlyoutOpen: false, + isAddingNewPrivateLocation: false, +}; + +export const agentPoliciesReducer = createReducer(initialState, (builder) => { + builder + .addCase(getAgentPoliciesAction.get, (state) => { + state.loading = true; + }) + .addCase(getAgentPoliciesAction.success, (state, action) => { + state.data = action.payload; + state.loading = false; + }) + .addCase(getAgentPoliciesAction.fail, (state, action) => { + state.error = action.payload; + state.loading = false; + }) + .addCase(setAddingNewPrivateLocation, (state, action) => { + state.isAddingNewPrivateLocation = action.payload; + }); +}); + +export * from './actions'; +export * from './effects'; +export * from './selectors'; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/selectors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/selectors.ts new file mode 100644 index 0000000000000..b24798394c65c --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/selectors.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createSelector } from 'reselect'; +import { AppState } from '..'; + +const getState = (appState: AppState) => appState.agentPolicies; +export const selectAgentPolicies = createSelector(getState, (state) => state); + +export const selectAddingNewPrivateLocation = (state: AppState) => + state.agentPolicies.isAddingNewPrivateLocation ?? false; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts index 4a2543b8f941c..ba3ead63ced0e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_effect.ts @@ -6,6 +6,7 @@ */ import { all, fork } from 'redux-saga/effects'; +import { fetchAgentPoliciesEffect } from './private_locations'; import { fetchNetworkEventsEffect } from './network_events/effects'; import { fetchSyntheticsMonitorEffect } from './monitor_details'; import { fetchIndexStatusEffect } from './index_status'; @@ -29,5 +30,6 @@ export const rootEffect = function* root(): Generator { fork(fetchOverviewStatusEffect), fork(fetchNetworkEventsEffect), fork(fetchPingStatusesEffect), + fork(fetchAgentPoliciesEffect), ]); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts index 6cd15c31f5287..39ce8975b0e78 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/root_reducer.ts @@ -7,6 +7,7 @@ import { combineReducers } from '@reduxjs/toolkit'; +import { agentPoliciesReducer, AgentPoliciesState } from './private_locations'; import { networkEventsReducer, NetworkEventsState } from './network_events'; import { monitorDetailsReducer, MonitorDetailsState } from './monitor_details'; import { uiReducer, UiState } from './ui'; @@ -30,6 +31,7 @@ export interface SyntheticsAppState { browserJourney: BrowserJourneyState; networkEvents: NetworkEventsState; pingStatus: PingStatusState; + agentPolicies: AgentPoliciesState; } export const rootReducer = combineReducers({ @@ -43,4 +45,5 @@ export const rootReducer = combineReducers({ browserJourney: browserJourneyReducer, networkEvents: networkEventsReducer, pingStatus: pingStatusReducer, + agentPolicies: agentPoliciesReducer, }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx index 95a2de56636e9..948c98136e601 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/synthetics_app.tsx @@ -92,6 +92,7 @@ const Application = (props: SyntheticsAppProps) => { observability: startPlugins.observability, cases: startPlugins.cases, spaces: startPlugins.spaces, + fleet: startPlugins.fleet, }} > diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts index e9d8fab054a53..14be3473bc791 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts @@ -111,6 +111,11 @@ export const mockState: SyntheticsAppState = { browserJourney: getBrowserJourneyMockSlice(), networkEvents: {}, pingStatus: getPingStatusesMockSlice(), + agentPolicies: { + loading: false, + error: null, + data: null, + }, }; function getBrowserJourneyMockSlice() { diff --git a/x-pack/plugins/synthetics/public/plugin.ts b/x-pack/plugins/synthetics/public/plugin.ts index f795523cf0fbb..4d96e407b3290 100644 --- a/x-pack/plugins/synthetics/public/plugin.ts +++ b/x-pack/plugins/synthetics/public/plugin.ts @@ -89,6 +89,7 @@ export interface ClientPluginsStart { docLinks: DocLinksStart; uiSettings: CoreStart['uiSettings']; usageCollection: UsageCollectionStart; + savedObjects: CoreStart['savedObjects']; } export interface UptimePluginServices extends Partial { diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/lib/lib.ts b/x-pack/plugins/synthetics/server/legacy_uptime/lib/lib.ts index 1018ae75cea48..12c67e5d346f5 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/lib/lib.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/lib/lib.ts @@ -5,13 +5,21 @@ * 2.0. */ -import { ElasticsearchClient, SavedObjectsClientContract, KibanaRequest } from '@kbn/core/server'; +import { + ElasticsearchClient, + SavedObjectsClientContract, + KibanaRequest, + CoreRequestHandlerContext, +} from '@kbn/core/server'; import chalk from 'chalk'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ESSearchResponse } from '@kbn/es-types'; import { RequestStatus } from '@kbn/inspector-plugin/common'; import { getInspectResponse } from '@kbn/observability-plugin/server'; import { InspectResponse } from '@kbn/observability-plugin/typings/common'; +import { enableInspectEsQueries } from '@kbn/observability-plugin/common'; +import { API_URLS } from '../../../common/constants'; +import { UptimeServerSetup } from './adapters'; import { UMLicenseCheck } from './domains'; import { UptimeRequests } from './requests'; import { savedObjectsAdapter } from './saved_objects/saved_objects'; @@ -40,22 +48,40 @@ export interface CountResponse { export type UptimeEsClient = ReturnType; -export const inspectableEsQueriesMap = new WeakMap(); - export function createUptimeESClient({ + isDev, + uiSettings, esClient, request, savedObjectsClient, - isInspectorEnabled, }: { - isInspectorEnabled?: boolean; + isDev?: boolean; + uiSettings?: CoreRequestHandlerContext['uiSettings']; esClient: ElasticsearchClient; request?: KibanaRequest; savedObjectsClient: SavedObjectsClientContract; }) { + const initSettings = async (self: UptimeEsClient) => { + if (!self.heartbeatIndices) { + const [isInspectorEnabled, dynamicSettings] = await Promise.all([ + getInspectEnabled(uiSettings), + savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient), + ]); + + self.heartbeatIndices = dynamicSettings?.heartbeatIndices || ''; + self.isInspectorEnabled = isInspectorEnabled; + + if (self.isInspectorEnabled || isDev) { + self.inspectableEsQueriesMap.set(request!, []); + } + } + }; + return { + inspectableEsQueriesMap: new WeakMap(), baseESClient: esClient, heartbeatIndices: '', + isInspectorEnabled: undefined as boolean | undefined, async search( params: TParams, operationName?: string, @@ -64,13 +90,7 @@ export function createUptimeESClient({ let res: any; let esError: any; - if (!this.heartbeatIndices) { - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( - savedObjectsClient! - ); - - this.heartbeatIndices = dynamicSettings?.heartbeatIndices || ''; - } + await initSettings(this); const esParams = { index: index ?? this.heartbeatIndices, ...params }; const startTime = process.hrtime(); @@ -87,7 +107,7 @@ export function createUptimeESClient({ esRequestStatus = RequestStatus.ERROR; } - const inspectableEsQueries = inspectableEsQueriesMap.get(request!); + const inspectableEsQueries = this.inspectableEsQueriesMap.get(request!); if (inspectableEsQueries) { inspectableEsQueries.push( @@ -101,7 +121,7 @@ export function createUptimeESClient({ startTime: startTimeNow, }) ); - if (request && isInspectorEnabled) { + if (request && this.isInspectorEnabled) { debugESCall({ startTime, request, esError, operationName: 'search', params: esParams }); } } @@ -116,13 +136,7 @@ export function createUptimeESClient({ let res: any; let esError: any; - if (!this.heartbeatIndices) { - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( - savedObjectsClient! - ); - - this.heartbeatIndices = dynamicSettings?.heartbeatIndices || ''; - } + await initSettings(this); const esParams = { index: this.heartbeatIndices, ...params }; const startTime = process.hrtime(); @@ -132,9 +146,9 @@ export function createUptimeESClient({ } catch (e) { esError = e; } - const inspectableEsQueries = inspectableEsQueriesMap.get(request!); + const inspectableEsQueries = this.inspectableEsQueriesMap.get(request!); - if (inspectableEsQueries && request && isInspectorEnabled) { + if (inspectableEsQueries && request && this.isInspectorEnabled) { debugESCall({ startTime, request, esError, operationName: 'count', params: esParams }); } @@ -147,9 +161,27 @@ export function createUptimeESClient({ getSavedObjectsClient() { return savedObjectsClient; }, + + getInspectData(path: string) { + const isInspectorEnabled = + (this.isInspectorEnabled || isDev) && path !== API_URLS.DYNAMIC_SETTINGS; + + if (isInspectorEnabled) { + return { _inspect: this.inspectableEsQueriesMap.get(request!) }; + } + return {}; + }, }; } +function getInspectEnabled(uiSettings?: CoreRequestHandlerContext['uiSettings']) { + if (!uiSettings) { + return false; + } + + return uiSettings.client.get(enableInspectEsQueries); +} + /* eslint-disable no-console */ function formatObj(obj: Record) { @@ -187,3 +219,7 @@ export function debugESCall({ } console.log(`\n`); } + +export const isTestUser = (server: UptimeServerSetup) => { + return server.config.service?.username === 'localKibanaIntegrationTestsUser'; +}; diff --git a/x-pack/plugins/synthetics/server/legacy_uptime/routes/uptime_route_wrapper.ts b/x-pack/plugins/synthetics/server/legacy_uptime/routes/uptime_route_wrapper.ts index a463cd1f9296e..854b03492adf3 100644 --- a/x-pack/plugins/synthetics/server/legacy_uptime/routes/uptime_route_wrapper.ts +++ b/x-pack/plugins/synthetics/server/legacy_uptime/routes/uptime_route_wrapper.ts @@ -6,11 +6,8 @@ */ import { KibanaResponse } from '@kbn/core-http-router-server-internal'; -import { enableInspectEsQueries } from '@kbn/observability-plugin/common'; import { UMKibanaRouteWrapper } from './types'; -import { createUptimeESClient, inspectableEsQueriesMap } from '../lib/lib'; - -import { API_URLS } from '../../../common/constants'; +import { createUptimeESClient, isTestUser } from '../lib/lib'; export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute, server) => ({ ...uptimeRoute, @@ -23,26 +20,16 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute, server) => server.authSavedObjectsClient = coreContext.savedObjects.client; - const isInspectorEnabled = await coreContext.uiSettings.client.get( - enableInspectEsQueries - ); - const uptimeEsClient = createUptimeESClient({ request, + isDev: server.isDev && !isTestUser(server), savedObjectsClient: coreContext.savedObjects.client, - isInspectorEnabled, esClient: esClient.asCurrentUser, + uiSettings: coreContext.uiSettings, }); server.uptimeEsClient = uptimeEsClient; - if ( - (isInspectorEnabled || server.isDev) && - server.config.service?.username !== 'localKibanaIntegrationTestsUser' - ) { - inspectableEsQueriesMap.set(request, []); - } - const res = await uptimeRoute.handler({ uptimeEsClient, savedObjectsClient: coreContext.savedObjects.client, @@ -59,9 +46,7 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute, server) => return response.ok({ body: { ...res, - ...((isInspectorEnabled || server.isDev) && uptimeRoute.path !== API_URLS.DYNAMIC_SETTINGS - ? { _inspect: inspectableEsQueriesMap.get(request) } - : {}), + ...uptimeEsClient.getInspectData(uptimeRoute.path), }, }); }, diff --git a/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts b/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts index fc1376e157607..108899271e1c5 100644 --- a/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts +++ b/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts @@ -5,11 +5,9 @@ * 2.0. */ import { KibanaResponse } from '@kbn/core-http-router-server-internal'; -import { enableInspectEsQueries } from '@kbn/observability-plugin/common'; -import { createUptimeESClient, inspectableEsQueriesMap } from './legacy_uptime/lib/lib'; +import { createUptimeESClient, isTestUser } from './legacy_uptime/lib/lib'; import { syntheticsServiceApiKey } from './legacy_uptime/lib/saved_objects/service_api_key'; import { SyntheticsRouteWrapper, SyntheticsStreamingRouteHandler } from './legacy_uptime/routes'; -import { API_URLS } from '../common/constants'; export const syntheticsRouteWrapper: SyntheticsRouteWrapper = ( uptimeRoute, @@ -31,26 +29,15 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = ( // specifically needed for the synthetics service api key generation server.authSavedObjectsClient = savedObjectsClient; - const isInspectorEnabled = await coreContext.uiSettings.client.get( - enableInspectEsQueries - ); - const uptimeEsClient = createUptimeESClient({ request, savedObjectsClient, - isInspectorEnabled, esClient: esClient.asCurrentUser, + uiSettings: coreContext.uiSettings, }); server.uptimeEsClient = uptimeEsClient; - if ( - (isInspectorEnabled || server.isDev) && - server.config.service?.username !== 'localKibanaIntegrationTestsUser' - ) { - inspectableEsQueriesMap.set(request, []); - } - const res = await (uptimeRoute.handler as SyntheticsStreamingRouteHandler)({ uptimeEsClient, savedObjectsClient, @@ -64,35 +51,26 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = ( return res; }, handler: async (context, request, response) => { - const coreContext = await context.core; - const { client: esClient } = coreContext.elasticsearch; - const savedObjectsClient = coreContext.savedObjects.getClient({ + const { elasticsearch, savedObjects, uiSettings } = await context.core; + + const { client: esClient } = elasticsearch; + const savedObjectsClient = savedObjects.getClient({ includedHiddenTypes: [syntheticsServiceApiKey.name], }); // specifically needed for the synthetics service api key generation server.authSavedObjectsClient = savedObjectsClient; - const isInspectorEnabled = await coreContext.uiSettings.client.get( - enableInspectEsQueries - ); - const uptimeEsClient = createUptimeESClient({ + isDev: Boolean(server.isDev) && !isTestUser(server), + uiSettings, request, savedObjectsClient, - isInspectorEnabled, esClient: esClient.asCurrentUser, }); server.uptimeEsClient = uptimeEsClient; - if ( - (isInspectorEnabled || server.isDev) && - server.config.service?.username !== 'localKibanaIntegrationTestsUser' - ) { - inspectableEsQueriesMap.set(request, []); - } - const res = await uptimeRoute.handler({ uptimeEsClient, savedObjectsClient, @@ -110,9 +88,7 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = ( return response.ok({ body: { ...res, - ...((isInspectorEnabled || server.isDev) && uptimeRoute.path !== API_URLS.DYNAMIC_SETTINGS - ? { _inspect: inspectableEsQueriesMap.get(request) } - : {}), + ...uptimeEsClient.getInspectData(uptimeRoute.path), }, }); }, 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 435ae4ba2a129..f3363b4d18a52 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 @@ -43,25 +43,26 @@ export const getAPIKeyForSyntheticsService = async ({ const apiKey = await syntheticsServiceAPIKeySavedObject.get(server); if (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; + const [isValid, { index }] = await Promise.all([ + server.security.authc.apiKeys.validate({ + id: apiKey.id, + api_key: apiKey.apiKey, + }), + 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 }; + } - if (!hasPermissions) { - return { isValid: false, apiKey }; - } - } else { + if (!isValid) { server.logger.info('Synthetics api is no longer valid'); } 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 cc80029fc960d..fbbc134a42c13 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 @@ -70,6 +70,12 @@ const defaultServices = { getUseCasesAddToNewCaseFlyout: () => {}, getUseCasesAddToExistingCaseModal: () => {}, }, + helpers: { + canUseCases: () => ({ + create: true, + update: true, + }), + }, }, } as unknown as CoreStart; 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 75da839c73dc0..c8901c5b9095d 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 @@ -13,20 +13,12 @@ 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 permissions: CasesPermissions = cases.helpers.canUseCases(); const queryClient = new QueryClient(); 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 index 1b112dc036dc3..98272a9d7add5 100644 --- 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 @@ -91,7 +91,100 @@ Object { } `; -exports[`AddToExistingCase should render the EuiContextMenuItem disabled 1`] = ` +exports[`AddToExistingCase should render the EuiContextMenuItem disabled if indicator is missing name 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 if user has no update permission 1`] = ` Object { "asFragment": [Function], "baseElement": 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 index 626e37b82a46a..c00f22e2d606a 100644 --- 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 @@ -10,31 +10,87 @@ 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'; +import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; +import { KibanaContext } from '../../../../hooks'; + +const indicator: Indicator = generateMockFileIndicator(); +const onClick = () => window.alert('clicked'); +const casesServiceMock = casesPluginMock.createStartContract(); describe('AddToExistingCase', () => { it('should render an EuiContextMenuItem', () => { - const indicator: Indicator = generateMockFileIndicator(); - const onClick = () => window.alert('clicked'); + const mockedServices = { + cases: { + ...casesServiceMock, + helpers: { + ...casesServiceMock.helpers, + canUseCases: () => ({ + create: true, + update: true, + }), + }, + }, + }; + const component = render( - + + + ); expect(component).toMatchSnapshot(); }); - it('should render the EuiContextMenuItem disabled', () => { - const indicator: Indicator = generateMockFileIndicator(); + it('should render the EuiContextMenuItem disabled if indicator is missing name', () => { + const mockedServices = { + cases: { + ...casesServiceMock, + helpers: { + ...casesServiceMock.helpers, + canUseCases: () => ({ + create: true, + update: true, + }), + }, + }, + }; + 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(); + }); + + it('should render the EuiContextMenuItem disabled if user has no update permission', () => { + const mockedServices = { + cases: { + ...casesServiceMock, + helpers: { + ...casesServiceMock.helpers, + canUseCases: () => ({ + create: false, + update: false, + }), + }, + }, + }; + + 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 index 1b620cde1a75b..3aff588be3192 100644 --- 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 @@ -9,7 +9,7 @@ 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 { useCaseDisabled } from '../../hooks/use_case_permission'; import { AttachmentMetadata, generateAttachmentsMetadata, @@ -52,20 +52,18 @@ export const AddToExistingCase: VFC = ({ 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 }); }; + const disabled: boolean = useCaseDisabled(attachmentMetadata.indicatorName); + return ( +
+ +
+ , + "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 if user have no create permission 1`] = ` Object { "asFragment": [Function], "baseElement": 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 index 44cce6e064a0c..95e5b283be344 100644 --- 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 @@ -5,35 +5,93 @@ * 2.0. */ +import { KibanaContext } from '../../../../hooks'; 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'; +import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; + +const indicator: Indicator = generateMockFileIndicator(); +const onClick = () => window.alert('clicked'); +const casesServiceMock = casesPluginMock.createStartContract(); describe('AddToNewCase', () => { it('should render an EuiContextMenuItem', () => { - const indicator: Indicator = generateMockFileIndicator(); - const onClick = () => window.alert('clicked'); + const mockedServices = { + cases: { + ...casesServiceMock, + helpers: { + ...casesServiceMock.helpers, + canUseCases: () => ({ + create: true, + update: true, + }), + }, + }, + }; + const component = render( - + + + ); expect(component).toMatchSnapshot(); }); - it('should render the EuiContextMenuItem disabled', () => { - const indicator: Indicator = generateMockFileIndicator(); + + it('should render the EuiContextMenuItem disabled if indicator is missing name', () => { + const mockedServices = { + cases: { + ...casesServiceMock, + helpers: { + ...casesServiceMock.helpers, + canUseCases: () => ({ + create: true, + update: true, + }), + }, + }, + }; + 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(); + }); + + it('should render the EuiContextMenuItem disabled if user have no create permission', () => { + const mockedServices = { + cases: { + ...casesServiceMock, + helpers: { + ...casesServiceMock.helpers, + canUseCases: () => ({ + create: false, + update: false, + }), + }, + }, + }; + 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 index abe2687787229..41772d1cadf71 100644 --- 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 @@ -9,7 +9,7 @@ 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 { useCaseDisabled } from '../../hooks/use_case_permission'; import { AttachmentMetadata, generateAttachmentsMetadata, @@ -52,20 +52,18 @@ export const AddToNewCase: VFC = ({ 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 }); }; + const disabled: boolean = useCaseDisabled(attachmentMetadata.indicatorName); + return ( { return ; }; }; + +// Note: This is for lazy loading +// eslint-disable-next-line import/no-default-export +export default initComponent(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_case_permission.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_case_permission.test.tsx new file mode 100644 index 0000000000000..d36bb6e076347 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_case_permission.test.tsx @@ -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 React, { FC, ReactNode } from 'react'; +import { Renderer, renderHook, RenderHookResult } from '@testing-library/react-hooks'; +import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; +import { KibanaContext } from '../../../hooks/use_kibana'; +import { useCaseDisabled } from './use_case_permission'; +import { TestProvidersComponent } from '../../../common/mocks/test_providers'; +import { EMPTY_VALUE } from '../../../common/constants'; + +const casesServiceMock = casesPluginMock.createStartContract(); + +const getProviderComponent = + (mockedServices: unknown) => + ({ children }: { children: ReactNode }) => + ( + + + {children} + + + ); + +describe('useCasePermission', () => { + let hookResult: RenderHookResult<{}, boolean, Renderer>; + + it('should return false if user has correct permissions and indicator has a name', () => { + const mockedServices = { + cases: { + ...casesServiceMock, + helpers: { + ...casesServiceMock.helpers, + canUseCases: () => ({ + create: true, + update: true, + }), + }, + }, + }; + // @ts-ignore + const ProviderComponent: FC = getProviderComponent(mockedServices); + + const indicatorName: string = 'abc'; + + hookResult = renderHook(() => useCaseDisabled(indicatorName), { + wrapper: ProviderComponent, + }); + expect(hookResult.result.current).toEqual(false); + }); + + it(`should return true if user doesn't have correct permissions`, () => { + const mockedServices = { + cases: { + ...casesServiceMock, + helpers: { + ...casesServiceMock.helpers, + canUseCases: () => ({ + create: false, + update: true, + }), + }, + }, + }; + // @ts-ignore + const ProviderComponent: FC = getProviderComponent(mockedServices); + + const indicatorName: string = 'abc'; + + hookResult = renderHook(() => useCaseDisabled(indicatorName), { + wrapper: ProviderComponent, + }); + expect(hookResult.result.current).toEqual(true); + }); + + it('should return true if indicator name is missing or empty', () => { + const mockedServices = { + cases: { + ...casesServiceMock, + helpers: { + ...casesServiceMock.helpers, + canUseCases: () => ({ + create: true, + update: true, + }), + }, + }, + }; + // @ts-ignore + const ProviderComponent: FC = getProviderComponent(mockedServices); + + const indicatorName: string = EMPTY_VALUE; + + hookResult = renderHook(() => useCaseDisabled(indicatorName), { + wrapper: ProviderComponent, + }); + expect(hookResult.result.current).toEqual(true); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_case_permission.ts b/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_case_permission.ts new file mode 100644 index 0000000000000..c2af16e1ac699 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/hooks/use_case_permission.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 { CasesPermissions } from '@kbn/cases-plugin/common'; +import { EMPTY_VALUE } from '../../../common/constants'; +import { useKibana } from '../../../hooks'; + +/** + * Decides if we enable or disable the add to existing and add to new case features. + * If the Indicator has no name the features will be disabled. + * If the user doesn't have the correct permissions the features will be disabled. + * + * @param indicatorName the name of the indicator + * @return true if the features are enabled + */ +export const useCaseDisabled = (indicatorName: string): boolean => { + const { cases } = useKibana().services; + const permissions: CasesPermissions = cases.helpers.canUseCases(); + + // disable the item if there is no indicator name or if the user doesn't have the right permission + // in the case's attachment, the indicator name is the link to open the flyout + const invalidIndicatorName: boolean = indicatorName === EMPTY_VALUE; + const hasPermission: boolean = permissions.create && permissions.update; + + return invalidIndicatorName || !hasPermission; +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/utils/attachments.spec.ts b/x-pack/plugins/threat_intelligence/public/modules/cases/utils/attachments.test.ts similarity index 96% rename from x-pack/plugins/threat_intelligence/public/modules/cases/utils/attachments.spec.ts rename to x-pack/plugins/threat_intelligence/public/modules/cases/utils/attachments.test.ts index aa03fed1ae8d7..f552465a7791c 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/cases/utils/attachments.spec.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/utils/attachments.test.ts @@ -48,9 +48,9 @@ describe('generateAttachmentsMetadata', () => { const result = generateAttachmentsMetadata(indicator); expect(result).toEqual({ - indicatorName: '', - indicatorType: '', - indicatorFeedName: '', + indicatorName: '-', + indicatorType: '-', + indicatorFeedName: '-', }); }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/cases/utils/attachments.tsx b/x-pack/plugins/threat_intelligence/public/modules/cases/utils/attachments.tsx index cf1905769e458..f5829379d03c1 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/cases/utils/attachments.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/cases/utils/attachments.tsx @@ -28,6 +28,10 @@ export interface AttachmentMetadata { indicatorFeedName: string; } +const AttachmentChildrenLazy = React.lazy( + () => import('../components/attachment_children/attachment_children') +); + /** * Create an {@link ExternalReferenceAttachmentType} object used to register an external reference * to the case plugin with our Threat Intelligence plugin initializes. @@ -49,14 +53,7 @@ export const generateAttachmentType = (): ExternalReferenceAttachmentType => ({ /> ), timelineAvatar: , - children: React.lazy(async () => { - const { initComponent } = await import( - '../components/attachment_children/attachment_children' - ); - return { - default: initComponent(), - }; - }), + children: AttachmentChildrenLazy, }), icon: 'crosshairs', }); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 72c21f85d4912..c7563b76b5a1a 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -5966,7 +5966,6 @@ "visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsText": "Nombre maximal de groupes pouvant être renvoyés par une source de données unique. Un nombre plus élevé pourra impacter négativement les performances de rendu du navigateur", "visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsTitle": "Nombre maximal de groupes pour la carte thermique", "visTypeVislib.aggResponse.allDocsTitle": "Tous les docs", - "visTypeVislib.functions.pie.help": "Visualisation du camembert", "visTypeVislib.functions.vislib.help": "Visualisation Vislib", "visTypeVislib.vislib.errors.noResultsFoundTitle": "Résultat introuvable", "visTypeVislib.vislib.legend.loadingLabel": "chargement…", @@ -18363,7 +18362,6 @@ "xpack.maps.tiles.resultsCompleteMsg": "{countPrefix}{count} documents trouvés.", "xpack.maps.tiles.resultsTrimmedMsg": "Résultats limités à {countPrefix}{count} documents.", "xpack.maps.tileStatusTracker.layerErrorMsg": "Impossible de charger {count} tuiles : {tileErrors}", - "xpack.maps.timeslider.setGlobalTime": "Définir l’heure globale sur {timeslice}", "xpack.maps.tooltip.joinPropertyTooltipContent": "La clé partagée \"{leftFieldName}\" est reliée à {rightSources}.", "xpack.maps.tooltip.pageNumerText": "{pageNumber} sur {total}", "xpack.maps.tooltipSelector.addLabelWithCount": "Ajouter {count}", @@ -18938,11 +18936,6 @@ "xpack.maps.tileMap.vis.title": "Carte de coordonnées", "xpack.maps.tiles.shapeCountMsg": " Ce nombre est approximatif.", "xpack.maps.tileStatusTracker.tileErrorMsg": "impossible de charger la tuile \"{tileZXYKey}\" : \"{status} {message}\"", - "xpack.maps.timeslider.closeLabel": "Fermer le curseur temporel", - "xpack.maps.timeslider.nextTimeWindowLabel": "Fenêtre temporelle suivante", - "xpack.maps.timeslider.pauseLabel": "Pause", - "xpack.maps.timeslider.playLabel": "Lecture", - "xpack.maps.timeslider.previousTimeWindowLabel": "Fenêtre temporelle précédente", "xpack.maps.timesliderToggleButton.closeLabel": "Fermer le curseur temporel", "xpack.maps.timesliderToggleButton.openLabel": "Ouvrir le curseur temporel", "xpack.maps.toolbarOverlay.drawBounds.initialGeometryLabel": "limites", @@ -19647,10 +19640,6 @@ "xpack.ml.anomalyResultsViewSelector.buttonGroupLegend": "Sélecteur de vue des résultats d'anomalie", "xpack.ml.anomalyResultsViewSelector.singleMetricViewerLabel": "Voir les résultats dans Single Metric Viewer", "xpack.ml.anomalySwimLane.noOverallDataMessage": "Aucune anomalie trouvée dans les résultats de groupe généraux pour cette plage temporelle", - "xpack.ml.anomalyUtils.multiBucketImpact.highLabel": "élevé", - "xpack.ml.anomalyUtils.multiBucketImpact.lowLabel": "bas", - "xpack.ml.anomalyUtils.multiBucketImpact.mediumLabel": "moyen", - "xpack.ml.anomalyUtils.multiBucketImpact.noneLabel": "aucun", "xpack.ml.anomalyUtils.severity.criticalLabel": "critique", "xpack.ml.anomalyUtils.severity.majorLabel": "majeur", "xpack.ml.anomalyUtils.severity.minorLabel": "mineure", @@ -21504,7 +21493,6 @@ "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.actualLabel": "réel", "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.lowerBoundsLabel": "limites inférieures", "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.upperBoundsLabel": "limites supérieures", - "xpack.ml.timeSeriesExplorer.timeSeriesChart.multiBucketImpactLabel": "impact sur plusieurs compartiments", "xpack.ml.timeSeriesExplorer.timeSeriesChart.typicalLabel": "typique", "xpack.ml.timeSeriesExplorer.timeSeriesChart.valueLabel": "valeur", "xpack.ml.timeSeriesExplorer.timeSeriesChart.withoutAnomalyScore.predictionLabel": "prédiction", @@ -24150,7 +24138,6 @@ "xpack.reporting.exportTypes.csv.generateCsv.esErrorMessage": "Réponse {statusCode} reçue d'Elasticsearch : {message}", "xpack.reporting.exportTypes.csv.generateCsv.unknownErrorMessage": "Une erreur inconnue est survenue : {message}", "xpack.reporting.jobsQuery.deleteError": "Impossible de supprimer le rapport : {error}", - "xpack.reporting.jobsQuery.infoError.unauthorizedErrorMessage": "Désolé, vous n’êtes pas autorisé à voir les informations {jobType}.", "xpack.reporting.jobStatusDetail.attemptXofY": "Tentative {attempts} sur {max_attempts}.", "xpack.reporting.jobStatusDetail.timeoutSeconds": "{timeout} secondes", "xpack.reporting.listing.diagnosticApiCallFailure": "Un problème est survenu lors de l'exécution du diagnostic : {error}", @@ -33392,9 +33379,6 @@ "visTypeMetric.params.style.styleTitle": "Style", "visTypeMetric.schemas.metricTitle": "Indicateur", "visTypeMetric.schemas.splitGroupTitle": "Diviser le groupe", - "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.deprecation": "La bibliothèque de graphiques héritée pour les camemberts dans Visualize est déclassée et ne sera plus compatible dans une prochaine version.", - "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.description": "Active la bibliothèque de graphiques héritée pour les camemberts dans Visualize.", - "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name": "Bibliothèque de graphiques héritée pour les camemberts", "visTypePie.controls.truncateLabel": "Tronquer", "visTypePie.controls.truncateTooltip": "Nombre de caractères pour les étiquettes positionnées en dehors du graphique.", "visTypePie.editors.pie.decimalSliderLabel": "Nombre maximal de décimales pour les pourcentages", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f1d682e10088c..70e41a8817ea9 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5960,7 +5960,6 @@ "visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsText": "1つのデータソースが返せるバケットの最大数です。値が大きいとブラウザのレンダリング速度が下がる可能性があります。", "visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsTitle": "ヒートマップの最大バケット数", "visTypeVislib.aggResponse.allDocsTitle": "すべてのドキュメント", - "visTypeVislib.functions.pie.help": "パイビジュアライゼーション", "visTypeVislib.functions.vislib.help": "Vislib ビジュアライゼーション", "visTypeVislib.vislib.errors.noResultsFoundTitle": "結果が見つかりませんでした", "visTypeVislib.vislib.legend.loadingLabel": "読み込み中…", @@ -18346,7 +18345,6 @@ "xpack.maps.tiles.resultsCompleteMsg": "{countPrefix}{count}個のドキュメントが見つかりました。", "xpack.maps.tiles.resultsTrimmedMsg": "結果は{countPrefix}{count}個のドキュメントに制限されています。", "xpack.maps.tileStatusTracker.layerErrorMsg": "{count}個のタイルを読み込めません:{tileErrors}", - "xpack.maps.timeslider.setGlobalTime": "グローバル時刻を{timeslice}に設定", "xpack.maps.tooltip.joinPropertyTooltipContent": "共有キー'{leftFieldName}'は{rightSources}と結合されます", "xpack.maps.tooltip.pageNumerText": "{total}ページ中 {pageNumber}ページ", "xpack.maps.tooltipSelector.addLabelWithCount": "{count} の追加", @@ -18921,11 +18919,6 @@ "xpack.maps.tileMap.vis.title": "座標マップ", "xpack.maps.tiles.shapeCountMsg": " この数は近似値です。", "xpack.maps.tileStatusTracker.tileErrorMsg": "タイル'{tileZXYKey}'を読み込めませんでした:'{status} {message}'", - "xpack.maps.timeslider.closeLabel": "時間スライダーを閉じる", - "xpack.maps.timeslider.nextTimeWindowLabel": "次の時間ウィンドウ", - "xpack.maps.timeslider.pauseLabel": "一時停止", - "xpack.maps.timeslider.playLabel": "再生", - "xpack.maps.timeslider.previousTimeWindowLabel": "前の時間ウィンドウ", "xpack.maps.timesliderToggleButton.closeLabel": "時間スライダーを閉じる", "xpack.maps.timesliderToggleButton.openLabel": "時間スライダーを開く", "xpack.maps.toolbarOverlay.drawBounds.initialGeometryLabel": "境界", @@ -19628,10 +19621,6 @@ "xpack.ml.anomalyResultsViewSelector.buttonGroupLegend": "異常結果ビューセレクター", "xpack.ml.anomalyResultsViewSelector.singleMetricViewerLabel": "シングルメトリックビューアーで結果を表示", "xpack.ml.anomalySwimLane.noOverallDataMessage": "この時間範囲のバケット結果全体で異常が見つかりませんでした", - "xpack.ml.anomalyUtils.multiBucketImpact.highLabel": "高", - "xpack.ml.anomalyUtils.multiBucketImpact.lowLabel": "低", - "xpack.ml.anomalyUtils.multiBucketImpact.mediumLabel": "中", - "xpack.ml.anomalyUtils.multiBucketImpact.noneLabel": "なし", "xpack.ml.anomalyUtils.severity.criticalLabel": "致命的", "xpack.ml.anomalyUtils.severity.majorLabel": "メジャー", "xpack.ml.anomalyUtils.severity.minorLabel": "マイナー", @@ -21485,7 +21474,6 @@ "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.actualLabel": "実際", "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.lowerBoundsLabel": "下の境界", "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.upperBoundsLabel": "上の境界", - "xpack.ml.timeSeriesExplorer.timeSeriesChart.multiBucketImpactLabel": "複数バケットの影響", "xpack.ml.timeSeriesExplorer.timeSeriesChart.typicalLabel": "通常", "xpack.ml.timeSeriesExplorer.timeSeriesChart.valueLabel": "値", "xpack.ml.timeSeriesExplorer.timeSeriesChart.withoutAnomalyScore.predictionLabel": "予測", @@ -24127,7 +24115,6 @@ "xpack.reporting.exportTypes.csv.generateCsv.esErrorMessage": "Elasticsearchから{statusCode}応答を受信しました:{message}", "xpack.reporting.exportTypes.csv.generateCsv.unknownErrorMessage": "不明なエラーが発生しました:{message}", "xpack.reporting.jobsQuery.deleteError": "レポートを削除できません:{error}", - "xpack.reporting.jobsQuery.infoError.unauthorizedErrorMessage": "{jobType}情報を表示する権限がありません", "xpack.reporting.jobStatusDetail.attemptXofY": "{attempts}/{max_attempts}回試行します。", "xpack.reporting.jobStatusDetail.timeoutSeconds": "{timeout}秒", "xpack.reporting.listing.diagnosticApiCallFailure": "診断の実行中に問題が発生しました:{error}", @@ -33366,9 +33353,6 @@ "visTypeMetric.params.style.styleTitle": "スタイル", "visTypeMetric.schemas.metricTitle": "メトリック", "visTypeMetric.schemas.splitGroupTitle": "グループを分割", - "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.deprecation": "Visualizeの円グラフのレガシーグラフライブラリは廃止予定であり、将来のバージョンではサポートされません。", - "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.description": "Visualizeで円グラフのレガシーグラフライブラリを有効にします。", - "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name": "円グラフのレガシーグラフライブラリ", "visTypePie.controls.truncateLabel": "切り捨て", "visTypePie.controls.truncateTooltip": "グラフ外に配置されたラベルの文字数。", "visTypePie.editors.pie.decimalSliderLabel": "割合の最大小数点桁数", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index dafbc3e4dbb31..164adec58674e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5967,7 +5967,6 @@ "visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsText": "单个数据源可以返回的最大存储桶数目。较高的数目可能对浏览器呈现性能有负面影响", "visTypeVislib.advancedSettings.visualization.heatmap.maxBucketsTitle": "热图最大存储桶数", "visTypeVislib.aggResponse.allDocsTitle": "所有文档", - "visTypeVislib.functions.pie.help": "饼图可视化", "visTypeVislib.functions.vislib.help": "Vislib 可视化", "visTypeVislib.vislib.errors.noResultsFoundTitle": "找不到结果", "visTypeVislib.vislib.legend.loadingLabel": "正在加载……", @@ -18371,7 +18370,6 @@ "xpack.maps.tiles.resultsCompleteMsg": "找到 {countPrefix}{count} 个文档。", "xpack.maps.tiles.resultsTrimmedMsg": "结果仅限于 {countPrefix}{count} 个文档。", "xpack.maps.tileStatusTracker.layerErrorMsg": "无法加载 {count} 个磁贴:{tileErrors}", - "xpack.maps.timeslider.setGlobalTime": "将全局时间设置为 {timeslice}", "xpack.maps.tooltip.joinPropertyTooltipContent": "共享密钥“{leftFieldName}”与 {rightSources} 联结", "xpack.maps.tooltip.pageNumerText": "第 {pageNumber} 页,共 {total} 页", "xpack.maps.tooltipSelector.addLabelWithCount": "添加 {count} 个", @@ -18946,11 +18944,6 @@ "xpack.maps.tileMap.vis.title": "坐标地图", "xpack.maps.tiles.shapeCountMsg": " 此计数为近似值。", "xpack.maps.tileStatusTracker.tileErrorMsg": "无法加载磁贴“{tileZXYKey}”:“{status} {message}”", - "xpack.maps.timeslider.closeLabel": "关闭时间滑块", - "xpack.maps.timeslider.nextTimeWindowLabel": "下一时间窗口", - "xpack.maps.timeslider.pauseLabel": "暂停", - "xpack.maps.timeslider.playLabel": "播放", - "xpack.maps.timeslider.previousTimeWindowLabel": "上一时间窗口", "xpack.maps.timesliderToggleButton.closeLabel": "关闭时间滑块", "xpack.maps.timesliderToggleButton.openLabel": "打开时间滑块", "xpack.maps.toolbarOverlay.drawBounds.initialGeometryLabel": "边界", @@ -19658,10 +19651,6 @@ "xpack.ml.anomalyResultsViewSelector.buttonGroupLegend": "异常结果视图选择器", "xpack.ml.anomalyResultsViewSelector.singleMetricViewerLabel": "在 Single Metric Viewer 中查看结果", "xpack.ml.anomalySwimLane.noOverallDataMessage": "此时间范围的总体存储桶中未发现异常", - "xpack.ml.anomalyUtils.multiBucketImpact.highLabel": "高", - "xpack.ml.anomalyUtils.multiBucketImpact.lowLabel": "低", - "xpack.ml.anomalyUtils.multiBucketImpact.mediumLabel": "中", - "xpack.ml.anomalyUtils.multiBucketImpact.noneLabel": "无", "xpack.ml.anomalyUtils.severity.criticalLabel": "紧急", "xpack.ml.anomalyUtils.severity.majorLabel": "重大", "xpack.ml.anomalyUtils.severity.minorLabel": "轻微", @@ -21515,7 +21504,6 @@ "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.actualLabel": "实际", "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.lowerBoundsLabel": "下边界", "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.upperBoundsLabel": "上边界", - "xpack.ml.timeSeriesExplorer.timeSeriesChart.multiBucketImpactLabel": "多存储桶影响", "xpack.ml.timeSeriesExplorer.timeSeriesChart.typicalLabel": "典型", "xpack.ml.timeSeriesExplorer.timeSeriesChart.valueLabel": "值", "xpack.ml.timeSeriesExplorer.timeSeriesChart.withoutAnomalyScore.predictionLabel": "预测", @@ -24159,7 +24147,6 @@ "xpack.reporting.exportTypes.csv.generateCsv.esErrorMessage": "从 Elasticsearch 收到 {statusCode} 响应:{message}", "xpack.reporting.exportTypes.csv.generateCsv.unknownErrorMessage": "出现未知错误:{message}", "xpack.reporting.jobsQuery.deleteError": "无法删除报告:{error}", - "xpack.reporting.jobsQuery.infoError.unauthorizedErrorMessage": "抱歉,您无权查看 {jobType} 信息", "xpack.reporting.jobStatusDetail.attemptXofY": "尝试 {attempts} 次,最多可尝试 {max_attempts} 次。", "xpack.reporting.jobStatusDetail.timeoutSeconds": "{timeout} 秒", "xpack.reporting.listing.diagnosticApiCallFailure": "运行诊断时出现问题:{error}", @@ -33403,9 +33390,6 @@ "visTypeMetric.params.style.styleTitle": "样式", "visTypeMetric.schemas.metricTitle": "指标", "visTypeMetric.schemas.splitGroupTitle": "拆分组", - "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.deprecation": "Visualize 中饼图的旧版图表库已过时,在未来版本中将不受支持。", - "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.description": "在 Visualize 中启用饼图的旧版图表库。", - "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name": "饼图旧版图表库", "visTypePie.controls.truncateLabel": "截断", "visTypePie.controls.truncateTooltip": "标签位于图表之外的字符数。", "visTypePie.editors.pie.decimalSliderLabel": "百分比的最大小数位数", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts index 86e3e60ef059e..e75dd56168687 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/types.ts @@ -15,8 +15,8 @@ export interface AlertsSearchBarProps { rangeFrom?: string; rangeTo?: string; query?: string; - onQueryChange: ({}: { + onQueryChange: (query: { dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' }; - query?: string; + query: string; }) => void; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.stories.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.stories.tsx index ab064893ae6fe..b40c0854e131f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.stories.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.stories.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { action } from '@storybook/addon-actions'; import { AlertsSummaryWidgetUI as Component } from './alert_summary_widget_ui'; export default { @@ -17,5 +18,6 @@ export const Overview = { active: 15, recovered: 53, timeRange: 'Last 30 days', + onClick: action('clicked'), }, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.tsx index e4003e803032d..ebeee35f5732d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/alert_summary_widget_ui.tsx @@ -5,9 +5,18 @@ * 2.0. */ +import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, AlertStatus } from '@kbn/rule-data-utils'; import { euiLightVars } from '@kbn/ui-theme'; -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import React from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import React, { MouseEvent } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { AlertsSummaryWidgetUIProps } from './types'; @@ -15,9 +24,26 @@ export const AlertsSummaryWidgetUI = ({ active, recovered, timeRange, + onClick, }: AlertsSummaryWidgetUIProps) => { + const handleClick = ( + event: MouseEvent, + status?: AlertStatus + ) => { + event.preventDefault(); + event.stopPropagation(); + + onClick(status); + }; + return ( - + @@ -43,39 +69,53 @@ export const AlertsSummaryWidgetUI = ({ - -

{active + recovered}

-
- - - + + +

{active + recovered}

+
+ + + +
- - -

{recovered}

+ ) => + handleClick(event, ALERT_STATUS_ACTIVE) + } + > + +

{active}

-
- - - + + + +
- -

{active}

-
- - - + ) => + handleClick(event, ALERT_STATUS_RECOVERED) + } + > + + +

{recovered}

+
+
+ + + +
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/types.ts index 81437071fcea2..545d33be36aeb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/components/types.ts @@ -5,8 +5,11 @@ * 2.0. */ +import { AlertStatus } from '@kbn/rule-data-utils'; + export interface AlertsSummaryWidgetUIProps { active: number; recovered: number; timeRange: JSX.Element | string; + onClick: (status?: AlertStatus) => void; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.test.tsx index 019d4f2ba549b..5fccea7961729 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.test.tsx @@ -59,6 +59,7 @@ describe('Rule Alert Summary', () => { ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.tsx index da43783568658..6cb64864e48ad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/rule_alerts_summary.tsx @@ -14,7 +14,7 @@ import { useLoadRuleTypes } from '../../../../hooks/use_load_rule_types'; import { RuleAlertsSummaryProps } from '.'; import { AlertSummaryWidgetError, AlertsSummaryWidgetUI } from './components'; -export const RuleAlertsSummary = ({ rule, filteredRuleTypes }: RuleAlertsSummaryProps) => { +export const RuleAlertsSummary = ({ rule, filteredRuleTypes, onClick }: RuleAlertsSummaryProps) => { const [features, setFeatures] = useState(''); const { ruleTypes } = useLoadRuleTypes({ filteredRuleTypes, @@ -40,6 +40,7 @@ export const RuleAlertsSummary = ({ rule, filteredRuleTypes }: RuleAlertsSummary return ( void; } 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 4c29d902e3883..6664aff57b0b3 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 @@ -1047,6 +1047,7 @@ export const RulesList = ({ setIsEnablingRules(false); showToast({ action: 'ENABLE', errors, total }); await refreshRules(); + onClearSelection(); }, [http, selectedIds, getFilter, setIsEnablingRules, showToast]); const onDisable = useCallback(async () => { @@ -1061,6 +1062,7 @@ export const RulesList = ({ setIsDisablingRules(false); showToast({ action: 'DISABLE', errors, total }); await refreshRules(); + onClearSelection(); }, [http, selectedIds, getFilter, setIsDisablingRules, showToast]); const onDeleteCancel = () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx index 81f62c9a3d28b..19fe6c68f2646 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx @@ -154,7 +154,7 @@ describe.skip('Rules list bulk disable', () => { wrapper.find('[data-test-subj="showBulkActionButton"]').first().simulate('click'); }); - it('can bulk disable', async () => { + it.skip('can bulk disable', async () => { wrapper.find('button[data-test-subj="bulkDisable"]').first().simulate('click'); await act(async () => { @@ -175,6 +175,10 @@ describe.skip('Rules list bulk disable', () => { ids: [], }) ); + expect( + wrapper.find('[data-test-subj="checkboxSelectRow-1"]').first().prop('checked') + ).toBeFalsy(); + expect(wrapper.find('button[data-test-subj="bulkDisable"]').exists()).toBeFalsy(); }); describe('Toast', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx index 5645b65fdbedc..688e0ae50ec46 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx @@ -175,6 +175,10 @@ describe.skip('Rules list bulk enable', () => { ids: [], }) ); + expect( + wrapper.find('[data-test-subj="checkboxSelectRow-1"]').first().prop('checked') + ).toBeFalsy(); + expect(wrapper.find('button[data-test-subj="bulkEnable"]').exists()).toBeFalsy(); }); describe('Toast', () => { diff --git a/x-pack/plugins/ux/e2e/journeys/utils.ts b/x-pack/plugins/ux/e2e/journeys/utils.ts index 7f915deda2f01..51043983deebc 100644 --- a/x-pack/plugins/ux/e2e/journeys/utils.ts +++ b/x-pack/plugins/ux/e2e/journeys/utils.ts @@ -10,7 +10,11 @@ import { expect, Page } from '@elastic/synthetics'; export async function waitForLoadingToFinish({ page }: { page: Page }) { while (true) { if ((await page.$(byTestId('kbnLoadingMessage'))) === null) break; - await page.waitForTimeout(5 * 1000); + await page.waitForTimeout(2 * 1000); + } + while (true) { + if ((await page.$(byTestId('globalLoadingIndicator'))) === null) break; + await page.waitForTimeout(2 * 1000); } } 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 2f72e9ea41da1..86b249dc6bc79 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 @@ -20,22 +20,17 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC await tearDown(getService); }); - // loadTestFile(require.resolve('./find')); - // loadTestFile(require.resolve('./create')); - // loadTestFile(require.resolve('./delete')); - // loadTestFile(require.resolve('./disable')); - // loadTestFile(require.resolve('./enable')); - // loadTestFile(require.resolve('./execution_status')); - // loadTestFile(require.resolve('./get')); - // loadTestFile(require.resolve('./get_alert_state')); - // loadTestFile(require.resolve('./get_alert_summary')); - // loadTestFile(require.resolve('./rule_types')); - // loadTestFile(require.resolve('./bulk_edit')); - // loadTestFile(require.resolve('./bulk_delete')); - // loadTestFile(require.resolve('./bulk_enable')); - // loadTestFile(require.resolve('./bulk_disable')); - // loadTestFile(require.resolve('./retain_api_key')); - loadTestFile(require.resolve('./clone')); + loadTestFile(require.resolve('./find')); + loadTestFile(require.resolve('./create')); + loadTestFile(require.resolve('./delete')); + loadTestFile(require.resolve('./disable')); + loadTestFile(require.resolve('./enable')); + loadTestFile(require.resolve('./execution_status')); + loadTestFile(require.resolve('./get')); + loadTestFile(require.resolve('./get_alert_state')); + loadTestFile(require.resolve('./get_alert_summary')); + loadTestFile(require.resolve('./rule_types')); + loadTestFile(require.resolve('./retain_api_key')); }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/config.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/config.ts new file mode 100644 index 0000000000000..f999da061b90b --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/config.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../common/config'; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig('security_and_spaces', { + disabledPlugins: [], + license: 'trial', + ssl: true, + enableActionsProxy: true, + publicBaseUrl: true, + testFiles: [require.resolve('./tests')], + useDedicatedTaskRunner: true, +}); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts similarity index 85% rename from x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_delete.ts rename to x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts index 91de3084993ff..b69b62e4b4a40 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_delete.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts @@ -10,7 +10,67 @@ import { UserAtSpaceScenarios, SuperuserAtSpace1 } from '../../../scenarios'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; -const defaultSuccessfulResponse = { errors: [], total: 1, taskIdsFailedToBeDeleted: [] }; +const getDefaultRules = (response: any) => ({ + id: response.body.rules[0].id, + apiKey: response.body.rules[0].apiKey, + notifyWhen: 'onThrottleInterval', + enabled: true, + name: 'abc', + tags: ['foo'], + consumer: 'alertsFixture', + throttle: '1m', + alertTypeId: 'test.noop', + apiKeyOwner: response.body.rules[0].apiKeyOwner, + createdBy: 'elastic', + updatedBy: response.body.rules[0].updatedBy, + muteAll: false, + mutedInstanceIds: [], + schedule: { interval: '1m' }, + actions: [], + params: {}, + snoozeSchedule: [], + updatedAt: response.body.rules[0].updatedAt, + createdAt: response.body.rules[0].createdAt, + scheduledTaskId: response.body.rules[0].scheduledTaskId, + executionStatus: response.body.rules[0].executionStatus, + monitoring: response.body.rules[0].monitoring, + ...(response.body.rules[0].nextRun ? { nextRun: response.body.rules[0].nextRun } : {}), + ...(response.body.rules[0].lastRun ? { lastRun: response.body.rules[0].lastRun } : {}), +}); + +const getThreeRules = (response: any) => { + const rules: any[] = []; + for (let i = 0; i < 3; i++) { + rules.push({ + id: response.body.rules[i].id, + apiKey: response.body.rules[i].apiKey, + notifyWhen: 'onThrottleInterval', + enabled: true, + name: 'abc', + tags: ['multiple-rules-delete'], + consumer: 'alertsFixture', + throttle: '1m', + alertTypeId: 'test.noop', + apiKeyOwner: response.body.rules[i].apiKeyOwner, + createdBy: 'elastic', + updatedBy: response.body.rules[i].updatedBy, + muteAll: false, + mutedInstanceIds: [], + schedule: { interval: '1m' }, + actions: [], + params: {}, + snoozeSchedule: [], + updatedAt: response.body.rules[i].updatedAt, + createdAt: response.body.rules[i].createdAt, + scheduledTaskId: response.body.rules[i].scheduledTaskId, + executionStatus: response.body.rules[i].executionStatus, + monitoring: response.body.rules[i].monitoring, + ...(response.body.rules[i].nextRun ? { nextRun: response.body.rules[i].nextRun } : {}), + ...(response.body.rules[i].lastRun ? { lastRun: response.body.rules[i].lastRun } : {}), + }); + } + return rules; +}; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { @@ -76,7 +136,12 @@ export default ({ getService }: FtrProviderContext) => { 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.body).to.eql({ + rules: [getDefaultRules(response)], + errors: [], + total: 1, + taskIdsFailedToBeDeleted: [], + }); expect(response.statusCode).to.eql(200); try { await getScheduledTask(createdRule1.scheduled_task_id); @@ -147,7 +212,18 @@ export default ({ getService }: FtrProviderContext) => { break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': - expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.body).to.eql({ + rules: [ + { + ...getDefaultRules(response), + alertTypeId: 'test.restricted-noop', + consumer: 'alertsRestrictedFixture', + }, + ], + errors: [], + total: 1, + taskIdsFailedToBeDeleted: [], + }); expect(response.statusCode).to.eql(200); try { await getScheduledTask(createdRule1.scheduled_task_id); @@ -207,7 +283,12 @@ export default ({ getService }: FtrProviderContext) => { await getScheduledTask(createdRule1.scheduled_task_id); break; case 'superuser at space1': - expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.body).to.eql({ + rules: [{ ...getDefaultRules(response), alertTypeId: 'test.restricted-noop' }], + errors: [], + total: 1, + taskIdsFailedToBeDeleted: [], + }); expect(response.statusCode).to.eql(200); try { await getScheduledTask(createdRule1.scheduled_task_id); @@ -267,7 +348,12 @@ export default ({ getService }: FtrProviderContext) => { 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.body).to.eql({ + rules: [{ ...getDefaultRules(response), consumer: 'alerts' }], + errors: [], + total: 1, + taskIdsFailedToBeDeleted: [], + }); expect(response.statusCode).to.eql(200); try { await getScheduledTask(createdRule1.scheduled_task_id); @@ -287,7 +373,7 @@ export default ({ getService }: FtrProviderContext) => { supertest .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') - .send(getTestRuleData({ tags: ['multiple-rules-edit'] })) + .send(getTestRuleData({ tags: ['multiple-rules-delete'] })) .expect(200) ) ); @@ -332,7 +418,12 @@ export default ({ getService }: FtrProviderContext) => { 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.body).to.eql({ + rules: getThreeRules(response), + errors: [], + total: 3, + taskIdsFailedToBeDeleted: [], + }); expect(response.statusCode).to.eql(200); for (const rule of rules) { try { @@ -399,7 +490,12 @@ export default ({ getService }: FtrProviderContext) => { 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.body).to.eql({ + rules: getThreeRules(response), + errors: [], + total: 3, + taskIdsFailedToBeDeleted: [], + }); expect(response.statusCode).to.eql(200); for (const rule of rules) { try { @@ -431,7 +527,12 @@ export default ({ getService }: FtrProviderContext) => { switch (scenario.id) { // This superuser has more privileges that we think case 'superuser at space1': - expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.body).to.eql({ + rules: [getDefaultRules(response)], + errors: [], + total: 1, + taskIdsFailedToBeDeleted: [], + }); expect(response.statusCode).to.eql(200); try { await getScheduledTask(createdRule.scheduled_task_id); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_disable.ts similarity index 100% rename from x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_disable.ts rename to x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_disable.ts diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_edit.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts similarity index 100% rename from x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_edit.ts rename to x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts 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/group3/tests/alerting/bulk_enable.ts similarity index 100% rename from x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_enable.ts rename to x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_enable.ts 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/group3/tests/alerting/clone.ts similarity index 100% rename from x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/clone.ts rename to x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/index.ts new file mode 100644 index 0000000000000..0dd1ec2531733 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/index.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { setupSpacesAndUsers, tearDown } from '../../../setup'; + +// eslint-disable-next-line import/no-default-export +export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) { + describe('Alerts - Group 3', () => { + describe('alerts', () => { + before(async () => { + await setupSpacesAndUsers(getService); + }); + + after(async () => { + await tearDown(getService); + }); + + loadTestFile(require.resolve('./bulk_edit')); + loadTestFile(require.resolve('./bulk_delete')); + loadTestFile(require.resolve('./bulk_enable')); + loadTestFile(require.resolve('./bulk_disable')); + loadTestFile(require.resolve('./clone')); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/index.ts new file mode 100644 index 0000000000000..c6b0d233a6041 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function alertingApiIntegrationTests({ loadTestFile }: FtrProviderContext) { + describe('alerting api integration security and spaces enabled - Group 3', function () { + loadTestFile(require.resolve('./alerting')); + }); +} diff --git a/x-pack/test/api_integration/apis/ml/modules/index.ts b/x-pack/test/api_integration/apis/ml/modules/index.ts index d28263b487701..6656183af8e6c 100644 --- a/x-pack/test/api_integration/apis/ml/modules/index.ts +++ b/x-pack/test/api_integration/apis/ml/modules/index.ts @@ -14,7 +14,8 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const fleetPackages = ['apache', 'nginx']; const installedPackages: Array<{ pkgName: string; version: string }> = []; - describe('modules', function () { + // failing: https://github.com/elastic/kibana/issues/102283 + describe.skip('modules', function () { before(async () => { // use await kibanaServer.savedObjects.cleanStandardList(); to make sure the fleet setup is removed correctly after the tests await kibanaServer.savedObjects.cleanStandardList(); diff --git a/x-pack/test/api_integration/apis/ml/system/index.ts b/x-pack/test/api_integration/apis/ml/system/index.ts index 68ffd5fa267e9..8b9aef9b813c9 100644 --- a/x-pack/test/api_integration/apis/ml/system/index.ts +++ b/x-pack/test/api_integration/apis/ml/system/index.ts @@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('system', function () { loadTestFile(require.resolve('./capabilities')); loadTestFile(require.resolve('./space_capabilities')); + loadTestFile(require.resolve('./index_exists')); }); } diff --git a/x-pack/test/api_integration/apis/ml/system/index_exists.ts b/x-pack/test/api_integration/apis/ml/system/index_exists.ts new file mode 100644 index 0000000000000..6cd68d82f0b88 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/system/index_exists.ts @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + const responseBody = { + ft_farequote_small: { exists: true }, + 'ft_farequote_*': { exists: true }, // wildcard + ft_farequote_fail: { exists: false }, + 'ft_farequote_fail_*': { exists: false }, // wildcard + }; + + const testDataList = [ + { + testTitle: 'as ML Poweruser', + user: USER.ML_POWERUSER, + requestBody: { + indices: Object.keys(responseBody), + }, + expected: { + responseCode: 200, + responseBody, + }, + }, + ]; + + const testDataListUnauthorized = [ + { + testTitle: 'as ML Viewer', + user: USER.ML_VIEWER, + requestBody: { + indices: Object.keys(responseBody), + }, + expected: { + responseCode: 403, + error: 'Forbidden', + }, + }, + { + testTitle: 'as ML Unauthorized user', + user: USER.ML_UNAUTHORIZED, + requestBody: { + jobIds: Object.keys(responseBody), + }, + expected: { + responseCode: 403, + error: 'Forbidden', + }, + }, + ]; + + async function runRequest(user: USER, requestBody: object, expectedStatusCode: number) { + const { body, status } = await supertest + .post('/api/ml/index_exists') + .auth(user, ml.securityCommon.getPasswordForUser(user)) + .set(COMMON_REQUEST_HEADERS) + .send(requestBody); + + ml.api.assertResponseStatusCode(expectedStatusCode, status, body); + return body; + } + + describe('POST ml/index_exists', function () { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote_small'); + }); + + describe('should correctly check if indices exist ', function () { + for (const testData of testDataList) { + it(`${testData.testTitle}`, async () => { + const body = await runRequest( + testData.user, + testData.requestBody, + testData.expected.responseCode + ); + const expectedResponse = testData.expected.responseBody; + expect(body).to.eql(expectedResponse); + }); + } + }); + + describe('rejects request', function () { + for (const testData of testDataListUnauthorized) { + describe('fails to check if indices exist', function () { + it(`${testData.testTitle}`, async () => { + const body = await runRequest( + testData.user, + testData.requestBody, + testData.expected.responseCode + ); + + expect(body).to.have.property('error').eql(testData.expected.error); + }); + }); + } + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts index fcaee57dd8166..60f3f8eca1bc8 100644 --- a/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts +++ b/x-pack/test/api_integration/apis/synthetics/add_monitor_project_legacy.ts @@ -1753,7 +1753,6 @@ export default function ({ getService }: FtrProviderContext) { { enabled: true, data_stream: { type: 'synthetics', dataset: 'http' }, - release: 'experimental', vars: { __ui: { value: '{"is_tls_enabled":false}', type: 'yaml' }, enabled: { value: false, type: 'bool' }, diff --git a/x-pack/test/api_integration/apis/uptime/rest/sample_data/test_policy.ts b/x-pack/test/api_integration/apis/uptime/rest/sample_data/test_policy.ts index 26418137fc23e..e7803bc48feee 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/sample_data/test_policy.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/sample_data/test_policy.ts @@ -31,7 +31,6 @@ export const getTestSyntheticsPolicy = ( { enabled: true, data_stream: { type: 'synthetics', dataset: 'http' }, - release: 'experimental', vars: { __ui: { value: @@ -133,7 +132,6 @@ export const getTestSyntheticsPolicy = ( streams: [ { enabled: false, - release: 'experimental', data_stream: { type: 'synthetics', dataset: 'tcp' }, vars: { __ui: { type: 'yaml' }, @@ -174,7 +172,6 @@ export const getTestSyntheticsPolicy = ( streams: [ { enabled: false, - release: 'experimental', data_stream: { type: 'synthetics', dataset: 'icmp' }, vars: { __ui: { type: 'yaml' }, @@ -206,7 +203,6 @@ export const getTestSyntheticsPolicy = ( streams: [ { enabled: true, - release: 'beta', data_stream: { type: 'synthetics', dataset: 'browser' }, vars: { __ui: { type: 'yaml' }, @@ -264,7 +260,6 @@ export const getTestSyntheticsPolicy = ( { enabled: true, data_stream: { type: 'synthetics', dataset: 'browser.network' }, - release: 'beta', id: 'synthetics/browser-browser.network-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', compiled_stream: { processors: [ @@ -276,7 +271,6 @@ export const getTestSyntheticsPolicy = ( { enabled: true, data_stream: { type: 'synthetics', dataset: 'browser.screenshot' }, - release: 'beta', id: 'synthetics/browser-browser.screenshot-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', compiled_stream: { processors: [ @@ -332,7 +326,6 @@ export const getTestProjectSyntheticsPolicy = ( { enabled: false, data_stream: { type: 'synthetics', dataset: 'http' }, - release: 'experimental', vars: { __ui: { type: 'yaml' }, enabled: { value: true, type: 'bool' }, @@ -382,7 +375,6 @@ export const getTestProjectSyntheticsPolicy = ( { enabled: false, data_stream: { type: 'synthetics', dataset: 'tcp' }, - release: 'experimental', vars: { __ui: { type: 'yaml' }, enabled: { value: true, type: 'bool' }, @@ -422,7 +414,6 @@ export const getTestProjectSyntheticsPolicy = ( streams: [ { enabled: false, - release: 'experimental', data_stream: { type: 'synthetics', dataset: 'icmp' }, vars: { __ui: { type: 'yaml' }, @@ -455,7 +446,6 @@ export const getTestProjectSyntheticsPolicy = ( { enabled: true, data_stream: { type: 'synthetics', dataset: 'browser' }, - release: 'beta', vars: { __ui: { value: @@ -547,7 +537,6 @@ export const getTestProjectSyntheticsPolicy = ( }, { enabled: true, - release: 'beta', data_stream: { type: 'synthetics', dataset: 'browser.network' }, id: `synthetics/browser-browser.network-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`, compiled_stream: { @@ -559,7 +548,6 @@ export const getTestProjectSyntheticsPolicy = ( }, { enabled: true, - release: 'beta', data_stream: { type: 'synthetics', dataset: 'browser.screenshot' }, id: `synthetics/browser-browser.screenshot-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`, compiled_stream: { diff --git a/x-pack/test/api_integration/apis/uptime/rest/uptime_zip_url_deprecation.ts b/x-pack/test/api_integration/apis/uptime/rest/uptime_zip_url_deprecation.ts index 29f13966f9578..1b34a713944e7 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/uptime_zip_url_deprecation.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/uptime_zip_url_deprecation.ts @@ -21,7 +21,6 @@ export default function (providerContext: FtrProviderContext) { streams: [ { enabled: true, - release: 'beta', data_stream: { type: 'synthetics', dataset: 'browser' }, vars: { __ui: { type: 'yaml' }, diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts index c9e850b360847..fd6e5ab18d5e2 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/delete_cases.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { defaultUser, getPostCaseRequest, postCommentUserReq } from '../../../../common/lib/mock'; +import { getPostCaseRequest, postCommentUserReq } from '../../../../common/lib/mock'; import { deleteCasesByESQuery, deleteCasesUserActions, @@ -17,7 +17,6 @@ import { deleteCases, createComment, getComment, - removeServerGeneratedPropertiesFromUserAction, getCase, superUserSpace1Auth, getCaseUserActions, @@ -53,7 +52,22 @@ export default ({ getService }: FtrProviderContext): void => { expect(body).to.eql({}); }); - it(`should delete a case's comments when that case gets deleted`, async () => { + it('should delete multiple cases and their user actions', async () => { + const [case1, case2] = await Promise.all([ + createCase(supertest, getPostCaseRequest()), + createCase(supertest, getPostCaseRequest()), + ]); + + await deleteCases({ supertest, caseIDs: [case1.id, case2.id] }); + + const userActionsCase1 = await getCaseUserActions({ supertest, caseID: case1.id }); + expect(userActionsCase1.length).to.be(0); + + const userActionsCase2 = await getCaseUserActions({ supertest, caseID: case2.id }); + expect(userActionsCase2.length).to.be(0); + }); + + it(`should delete a case's comments and user actions when that case gets deleted`, async () => { const postedCase = await createCase(supertest, getPostCaseRequest()); const patchedCase = await createComment({ supertest, @@ -76,23 +90,16 @@ export default ({ getService }: FtrProviderContext): void => { commentId: patchedCase.comments![0].id, expectedHttpCode: 404, }); + + const userActions = await getCaseUserActions({ supertest, caseID: postedCase.id }); + expect(userActions.length).to.be(0); }); - it('should create a user action when deleting a case', async () => { + it('should delete all user actions when deleting a case', async () => { const postedCase = await createCase(supertest, getPostCaseRequest()); await deleteCases({ supertest, caseIDs: [postedCase.id] }); const userActions = await getCaseUserActions({ supertest, caseID: postedCase.id }); - const creationUserAction = removeServerGeneratedPropertiesFromUserAction(userActions[1]); - - expect(creationUserAction).to.eql({ - action: 'delete', - type: 'delete_case', - created_by: defaultUser, - case_id: postedCase.id, - comment_id: null, - payload: {}, - owner: 'securitySolutionFixture', - }); + expect(userActions.length).to.be(0); }); it('unhappy path - 404s when case is not there', async () => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts index aacb5f6c8ae17..c03831b57fd1e 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts @@ -68,19 +68,12 @@ export default ({ getService }: FtrProviderContext): void => { expect(createCaseUserAction.payload.connector).to.eql(postCaseReq.connector); }); - it('creates a delete case user action when a case is deleted', async () => { + it('deletes all user actions when a case is deleted', async () => { const theCase = await createCase(supertest, postCaseReq); await deleteCases({ supertest, caseIDs: [theCase.id] }); const userActions = await getCaseUserActions({ supertest, caseID: theCase.id }); - const userAction = userActions[1]; - - // One for creation and one for deletion - expect(userActions.length).to.eql(2); - - expect(userAction.action).to.eql('delete'); - expect(userAction.type).to.eql('delete_case'); - expect(userAction.payload).to.eql({}); + expect(userActions.length).to.be(0); }); it('creates a status update user action when changing the status', async () => { diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/hidden_saved_object_routes.ts b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/hidden_saved_object_routes.ts index 7b4a79aee2223..9f1c0dee955fa 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/hidden_saved_object_routes.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/hidden_saved_object_routes.ts @@ -68,6 +68,8 @@ export function registerHiddenSORoutes( savedObjects = [...savedObjects, ...result.saved_objects]; } + await finder.close(); + try { return response.ok({ body: { saved_objects: savedObjects }, diff --git a/x-pack/test/fleet_api_integration/apis/index.js b/x-pack/test/fleet_api_integration/apis/index.js index f264a4fb8134a..60235bb6fc482 100644 --- a/x-pack/test/fleet_api_integration/apis/index.js +++ b/x-pack/test/fleet_api_integration/apis/index.js @@ -41,9 +41,6 @@ export default function ({ loadTestFile, getService }) { // Settings loadTestFile(require.resolve('./settings')); - // Preconfiguration - loadTestFile(require.resolve('./preconfiguration')); - // Service tokens loadTestFile(require.resolve('./service_tokens')); diff --git a/x-pack/test/fleet_api_integration/apis/preconfiguration/preconfiguration.ts b/x-pack/test/fleet_api_integration/apis/preconfiguration/preconfiguration.ts deleted file mode 100644 index 83d2913e7aa59..0000000000000 --- a/x-pack/test/fleet_api_integration/apis/preconfiguration/preconfiguration.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { PRECONFIGURATION_API_ROUTES } from '@kbn/fleet-plugin/common/constants'; -import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; -import { skipIfNoDockerRegistry } from '../../helpers'; - -export default function (providerContext: FtrProviderContext) { - const { getService } = providerContext; - const supertest = getService('supertest'); - - // use function () {} and not () => {} here - // because `this` has to point to the Mocha context - // see https://mochajs.org/#arrow-functions - - describe('Preconfiguration', async () => { - skipIfNoDockerRegistry(providerContext); - before(async () => { - await getService('kibanaServer').savedObjects.cleanStandardList(); - - await getService('esArchiver').load( - 'x-pack/test/functional/es_archives/fleet/empty_fleet_server' - ); - }); - - after(async () => { - await getService('esArchiver').unload( - 'x-pack/test/functional/es_archives/fleet/empty_fleet_server' - ); - await getService('kibanaServer').savedObjects.cleanStandardList(); - }); - - // Basic health check for the API; functionality is covered by the unit tests - it('should succeed with an empty payload', async () => { - const { body } = await supertest - .put(PRECONFIGURATION_API_ROUTES.UPDATE_PATTERN) - .set('kbn-xsrf', 'xxxx') - .send({}) - .expect(200); - - expect(body.nonFatalErrors).to.eql([]); - }); - }); -} diff --git a/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts b/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts index 89be0d8ccbd70..900b05f46531a 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts @@ -65,14 +65,17 @@ export default ({ getService }: FtrProviderContext) => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/146450 - describe.skip('Create rule button', () => { + describe('Create rule button', () => { it('Show Create Rule flyout when Create Rule button is clicked', async () => { await observability.alerts.common.navigateToRulesPage(); await retry.waitFor( 'Create Rule button is visible', async () => await testSubjects.exists('createRuleButton') ); + await retry.waitFor( + 'Create Rule button is enabled', + async () => await testSubjects.isEnabled('createRuleButton') + ); await observability.alerts.rulesPage.clickCreateRuleButton(); await retry.waitFor( 'Create Rule flyout is visible', diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/usage/api_counters.ts b/x-pack/test/reporting_api_integration/reporting_and_security/usage/api_counters.ts index eeeba2130629f..93b7d4d71fafb 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/usage/api_counters.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/usage/api_counters.ts @@ -12,8 +12,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { const log = getService('log'); - const supertest = getService('supertest'); - const supertestUnauth = getService('supertestWithoutAuth'); + const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); const usageAPI = getService('usageAPI'); const reportingAPI = getService('reportingAPI'); @@ -22,11 +21,13 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { await esArchiver.emptyKibanaIndex(); await reportingAPI.initEcommerce(); + await esArchiver.load('x-pack/test/functional/es_archives/reporting/archived_reports'); }); after(async () => { await reportingAPI.deleteAllReports(); await reportingAPI.teardownEcommerce(); + await esArchiver.unload('x-pack/test/functional/es_archives/reporting/archived_reports'); }); describe('server', function () { @@ -43,7 +44,7 @@ export default function ({ getService }: FtrProviderContext) { enum paths { LIST = '/api/reporting/jobs/list', COUNT = '/api/reporting/jobs/count', - INFO = '/api/reporting/jobs/info/{docId}', + INFO = '/api/reporting/jobs/info/kraz0qle154g0763b569zz83', ILM = '/api/reporting/ilm_policy_status', DIAG_BROWSER = '/api/reporting/diagnose/browser', DIAG_SCREENSHOT = '/api/reporting/diagnose/screenshot', @@ -58,7 +59,11 @@ export default function ({ getService }: FtrProviderContext) { await Promise.all( Object.keys(paths).map(async (key) => { - await Promise.all([...Array(CALL_COUNT)].map(() => supertest.get((paths as any)[key]))); + await Promise.all( + [...Array(CALL_COUNT)].map(() => + supertest.get(paths[key as keyof typeof paths]).auth('test_user', 'changeme') + ) + ); }) ); @@ -80,30 +85,26 @@ export default function ({ getService }: FtrProviderContext) { }); it('job info', async () => { - expect(getUsageCount(initialStats, `get ${paths.INFO}`)).to.be(0); - expect(getUsageCount(stats, `get ${paths.INFO}`)).to.be(CALL_COUNT); + expect( + getUsageCount(initialStats, `get /api/reporting/jobs/info/{docId}:printable_pdf`) + ).to.be(0); + expect(getUsageCount(stats, `get /api/reporting/jobs/info/{docId}:printable_pdf`)).to.be( + CALL_COUNT + ); }); }); describe('downloading and deleting', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/archived_reports'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/archived_reports'); - }); - it('downloading', async () => { try { await Promise.all([ - supertestUnauth + supertest .get('/api/reporting/jobs/download/kraz0qle154g0763b569zz83') .auth('test_user', 'changeme'), - supertestUnauth + supertest .get('/api/reporting/jobs/download/kraz0vj4154g0763b5curq51') .auth('test_user', 'changeme'), - supertestUnauth + supertest .get('/api/reporting/jobs/download/k9a9rq1i0gpe1457b17s7yc6') .auth('test_user', 'changeme'), ]); @@ -117,7 +118,10 @@ export default function ({ getService }: FtrProviderContext) { log.info(`calling getUsageStats...`); expect( - getUsageCount(await usageAPI.getUsageStats(), `get /api/reporting/jobs/download/{docId}`) + getUsageCount( + await usageAPI.getUsageStats(), + `get /api/reporting/jobs/download/{docId}:printable_pdf` + ) ).to.be(3); }); @@ -125,7 +129,7 @@ export default function ({ getService }: FtrProviderContext) { log.info(`sending 1 delete request...`); try { - await supertestUnauth + await supertest .delete('/api/reporting/jobs/delete/krazcyw4156m0763b503j7f9') .auth('test_user', 'changeme') .set('kbn-xsrf', 'xxx'); @@ -140,7 +144,10 @@ export default function ({ getService }: FtrProviderContext) { log.info(`calling getUsageStats...`); expect( - getUsageCount(await usageAPI.getUsageStats(), `delete /api/reporting/jobs/delete/{docId}`) + getUsageCount( + await usageAPI.getUsageStats(), + `delete /api/reporting/jobs/delete/{docId}:csv_searchsource` + ) ).to.be(1); }); }); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/usage/index.ts b/x-pack/test/reporting_api_integration/reporting_and_security/usage/index.ts index 420ec0b1c7444..f43ca39136dcb 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/usage/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/usage/index.ts @@ -9,13 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ getService, loadTestFile }: FtrProviderContext) { - const reportingAPI = getService('reportingAPI'); - describe('Usage', () => { - const deleteAllReports = () => reportingAPI.deleteAllReports(); - beforeEach(deleteAllReports); - after(deleteAllReports); - loadTestFile(require.resolve('./archived_data')); loadTestFile(require.resolve('./initial')); loadTestFile(require.resolve('./metrics')); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/usage/new_jobs.ts b/x-pack/test/reporting_api_integration/reporting_and_security/usage/new_jobs.ts index e702be05f9bd8..0377b277defbb 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/usage/new_jobs.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/usage/new_jobs.ts @@ -38,12 +38,7 @@ export default function ({ getService }: FtrProviderContext) { ); const usage = await usageAPI.getUsageStats(); - reportingAPI.expectRecentPdfAppStats(usage, 'visualization', 0); - reportingAPI.expectRecentPdfAppStats(usage, 'dashboard', 0); - reportingAPI.expectRecentPdfLayoutStats(usage, 'preserve_layout', 0); - reportingAPI.expectRecentPdfLayoutStats(usage, 'print', 0); reportingAPI.expectRecentJobTypeTotalStats(usage, 'csv_searchsource', 1); - reportingAPI.expectRecentJobTypeTotalStats(usage, 'printable_pdf', 0); }); it('should handle preserve_layout pdf', async () => { @@ -55,12 +50,8 @@ export default function ({ getService }: FtrProviderContext) { ); const usage = await usageAPI.getUsageStats(); - reportingAPI.expectRecentPdfAppStats(usage, 'visualization', 1); - reportingAPI.expectRecentPdfAppStats(usage, 'dashboard', 1); reportingAPI.expectRecentPdfLayoutStats(usage, 'preserve_layout', 2); - reportingAPI.expectRecentPdfLayoutStats(usage, 'print', 0); - reportingAPI.expectRecentJobTypeTotalStats(usage, 'csv_searchsource', 0); - reportingAPI.expectRecentJobTypeTotalStats(usage, 'printable_pdf', 2); + reportingAPI.expectAllTimePdfLayoutStats(usage, 'preserve_layout', 2); }); it('should handle print_layout pdf', async () => { @@ -72,19 +63,8 @@ export default function ({ getService }: FtrProviderContext) { ); const usage = await usageAPI.getUsageStats(); - reportingAPI.expectRecentPdfAppStats(usage, 'visualization', 1); - reportingAPI.expectRecentPdfAppStats(usage, 'dashboard', 1); - reportingAPI.expectRecentPdfLayoutStats(usage, 'preserve_layout', 1); reportingAPI.expectRecentPdfLayoutStats(usage, 'print', 1); - reportingAPI.expectRecentJobTypeTotalStats(usage, 'csv_searchsource', 0); - reportingAPI.expectRecentJobTypeTotalStats(usage, 'printable_pdf', 2); - - reportingAPI.expectAllTimePdfAppStats(usage, 'visualization', 1); - reportingAPI.expectAllTimePdfAppStats(usage, 'dashboard', 1); - reportingAPI.expectAllTimePdfLayoutStats(usage, 'preserve_layout', 1); reportingAPI.expectAllTimePdfLayoutStats(usage, 'print', 1); - reportingAPI.expectAllTimeJobTypeTotalStats(usage, 'csv_searchsource', 0); - reportingAPI.expectAllTimeJobTypeTotalStats(usage, 'printable_pdf', 2); }); }); } diff --git a/x-pack/test/scalability/config.ts b/x-pack/test/scalability/config.ts index 49bcfee2cf199..0d3303faf5970 100644 --- a/x-pack/test/scalability/config.ts +++ b/x-pack/test/scalability/config.ts @@ -62,8 +62,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...(!!AGGS_SHARD_DELAY ? ['--data.search.aggs.shardDelay.enabled=true'] : []), ...(!!DISABLE_PLUGINS ? ['--plugins.initialize=false'] : []), ], - // delay shutdown to ensure that APM can report the data it collects during test execution - delayShutdown: 90_000, }, }; } diff --git a/x-pack/test/scalability/runner.ts b/x-pack/test/scalability/runner.ts index e09a9d438b410..5882237ade467 100644 --- a/x-pack/test/scalability/runner.ts +++ b/x-pack/test/scalability/runner.ts @@ -6,6 +6,7 @@ */ import { withProcRunner } from '@kbn/dev-proc-runner'; +import path from 'path'; import { FtrProviderContext } from './ftr_provider_context'; /** @@ -19,6 +20,7 @@ export async function ScalabilityTestRunner( gatlingProjectRootPath: string ) { const log = getService('log'); + const gatlingReportBaseDir = path.parse(scalabilityJsonPath).name; log.info(`Running scalability test with json file: '${scalabilityJsonPath}'`); @@ -28,6 +30,7 @@ export async function ScalabilityTestRunner( args: [ 'gatling:test', '-q', + `-Dgatling.core.outputDirectoryBaseName=${gatlingReportBaseDir}`, '-Dgatling.simulationClass=org.kibanaLoadTest.simulation.generic.GenericJourney', `-DjourneyPath=${scalabilityJsonPath}`, ], diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/finding_anomalies.ts b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/finding_anomalies.ts new file mode 100644 index 0000000000000..96b6877aec598 --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/finding_anomalies.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +import { ECOMMERCE_INDEX_PATTERN } from '..'; + +export default function ({ getPageObject, getService }: FtrProviderContext) { + const elasticChart = getService('elasticChart'); + const ml = getService('ml'); + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['ml_docs', 'anomaly_detection']; + + describe('finding anomalies', function () { + after(async () => { + await elasticChart.setNewChartUiDebugFlag(false); + await ml.api.cleanMlIndices(); + }); + + it('ecommerce job wizards screenshot', async () => { + await ml.testExecution.logTestStep('navigate to job list'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.testExecution.logTestStep('load the wizards'); + await ml.jobManagement.navigateToNewJobSourceSelection(); + await ml.jobSourceSelection.selectSourceForAnomalyDetectionJob(ECOMMERCE_INDEX_PATTERN); + + await ml.testExecution.logTestStep('take screenshot'); + await commonScreenshots.removeFocusFromElement(); + await commonScreenshots.takeScreenshot('ml-create-job', screenshotDirectories); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/index.ts b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/index.ts index 4d092c36d6545..3d83fd1cfde70 100644 --- a/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/index.ts +++ b/x-pack/test/screenshot_creation/apps/ml_docs/anomaly_detection/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./population_analysis')); loadTestFile(require.resolve('./custom_urls')); loadTestFile(require.resolve('./mapping_anomalies')); + loadTestFile(require.resolve('./finding_anomalies')); }); } diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index 730486ccf94f5..92ce0a8aaf6d9 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -40,7 +40,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'Warning', 'Linux', '10.2.17.24, 10.56.215.200,10.254.196.130', - '8.5.0', + 'x', 'x', '', ], @@ -51,7 +51,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'Success', 'Linux', '10.138.79.131, 10.170.160.154', - '8.5.0', + 'x', 'x', '', ], @@ -62,7 +62,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'Warning', 'Linux', '10.87.11.145, 10.117.106.109,10.242.136.97', - '8.5.0', + 'x', 'x', '', ], @@ -75,6 +75,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { for (let i = 1; i < tableData.length; i++) { tableData[i][1] = 'x'; tableData[i][2] = 'x'; + tableData[i][6] = 'x'; tableData[i][7] = 'x'; } @@ -96,8 +97,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - // Version specific: https://github.com/elastic/kibana/issues/141298 - describe.skip('when there is data,', () => { + describe('when there is data,', () => { before(async () => { indexedData = await endpointTestResources.loadEndpointData({ numHosts: 3 }); await pageObjects.endpoint.navigateToEndpointList(); diff --git a/yarn.lock b/yarn.lock index a0b8800f50d46..c0968101f2fe9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -113,21 +113,21 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.20.2", "@babel/core@^7.7.2", "@babel/core@^7.7.5": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.2.tgz#8dc9b1620a673f92d3624bd926dc49a52cf25b92" - integrity sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g== +"@babel/core@^7.1.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.20.5", "@babel/core@^7.7.2", "@babel/core@^7.7.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.5.tgz#45e2114dc6cd4ab167f81daf7820e8fa1250d113" + integrity sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.2" + "@babel/generator" "^7.20.5" "@babel/helper-compilation-targets" "^7.20.0" "@babel/helper-module-transforms" "^7.20.2" - "@babel/helpers" "^7.20.1" - "@babel/parser" "^7.20.2" + "@babel/helpers" "^7.20.5" + "@babel/parser" "^7.20.5" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.2" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -150,12 +150,12 @@ dependencies: eslint-rule-composer "^0.3.0" -"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.20.1", "@babel/generator@^7.20.2", "@babel/generator@^7.20.4", "@babel/generator@^7.7.2": - version "7.20.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8" - integrity sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA== +"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.20.5", "@babel/generator@^7.7.2": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95" + integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA== dependencies: - "@babel/types" "^7.20.2" + "@babel/types" "^7.20.5" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -370,14 +370,14 @@ "@babel/traverse" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.20.1": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.1.tgz#2ab7a0fcb0a03b5bf76629196ed63c2d7311f4c9" - integrity sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.20.5": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.6.tgz#e64778046b70e04779dfbdf924e7ebb45992c763" + integrity sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w== dependencies: "@babel/template" "^7.18.10" - "@babel/traverse" "^7.20.1" - "@babel/types" "^7.20.0" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" @@ -388,10 +388,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.20.1", "@babel/parser@^7.20.2", "@babel/parser@^7.20.3": - version "7.20.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2" - integrity sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg== +"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8" + integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -1177,12 +1177,12 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.1", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9" - integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.6", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3" + integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA== dependencies: - regenerator-runtime "^0.13.10" + regenerator-runtime "^0.13.11" "@babel/template@^7.12.7", "@babel/template@^7.18.10", "@babel/template@^7.18.6", "@babel/template@^7.3.3": version "7.18.10" @@ -1193,26 +1193,26 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.10.3", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8" - integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== +"@babel/traverse@^7.10.3", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133" + integrity sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.1" + "@babel/generator" "^7.20.5" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.1" - "@babel/types" "^7.20.0" + "@babel/parser" "^7.20.5" + "@babel/types" "^7.20.5" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842" - integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog== +"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84" + integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg== dependencies: "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" @@ -1988,7 +1988,7 @@ resolved "https://registry.yarnpkg.com/@foliojs-fork/restructure/-/restructure-2.0.2.tgz#73759aba2aff1da87b7c4554e6839c70d43c92b4" integrity sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA== -"@gar/promisify@^1.0.1": +"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== @@ -4561,6 +4561,14 @@ "@gar/promisify" "^1.0.1" semver "^7.3.5" +"@npmcli/fs@^2.1.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" + integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== + dependencies: + "@gar/promisify" "^1.1.3" + semver "^7.3.5" + "@npmcli/move-file@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" @@ -4568,6 +4576,14 @@ dependencies: mkdirp "^1.0.4" +"@npmcli/move-file@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" + integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + "@octokit/auth-token@^2.4.0": version "2.4.4" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.4.tgz#ee31c69b01d0378c12fd3ffe406030f3d94d3b56" @@ -9295,7 +9311,7 @@ axobject-query@^2.2.0: resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== -babel-jest@^29.2.2, babel-jest@^29.3.1: +babel-jest@^29.3.1: version "29.3.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.3.1.tgz#05c83e0d128cd48c453eea851482a38782249f44" integrity sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA== @@ -10061,6 +10077,30 @@ cacache@^15.0.3, cacache@^15.0.4, cacache@^15.0.5, cacache@^15.2.0: tar "^6.0.2" unique-filename "^1.1.1" +cacache@^16.1.0: + version "16.1.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" + integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== + dependencies: + "@npmcli/fs" "^2.1.0" + "@npmcli/move-file" "^2.0.0" + chownr "^2.0.0" + fs-minipass "^2.1.0" + glob "^8.0.1" + infer-owner "^1.0.4" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + mkdirp "^1.0.4" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^9.0.0" + tar "^6.1.11" + unique-filename "^2.0.0" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -12080,7 +12120,7 @@ debug@3.X, debug@^3.0.0, debug@^3.1.0, debug@^3.2.7: dependencies: ms "^2.1.1" -debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -12819,22 +12859,7 @@ ejs@^3.1.6, ejs@^3.1.8: dependencies: jake "^10.8.5" -elastic-apm-http-client@11.0.2, elastic-apm-http-client@^11.0.1: - version "11.0.2" - resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-11.0.2.tgz#576521443d4f3c733b5220ae8175bf5538870cf5" - integrity sha512-Wiqwi4lnhjkILtP54wIbdY0X3Lv+x9JID42zYBI3g7BGRWUu4pPcTjJStWT/muMW57cdimHUektD3tOMFogprQ== - dependencies: - agentkeepalive "^4.2.1" - breadth-filter "^2.0.0" - end-of-stream "^1.4.4" - fast-safe-stringify "^2.0.7" - fast-stream-to-buffer "^1.0.0" - object-filter-sequence "^1.0.0" - readable-stream "^3.4.0" - semver "^6.3.0" - stream-chopper "^3.0.1" - -elastic-apm-http-client@11.0.3: +elastic-apm-http-client@11.0.3, elastic-apm-http-client@^11.0.1: version "11.0.3" resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-11.0.3.tgz#1d357af449d66695ef10019c21efe6377ad8815e" integrity sha512-y+P9ByvfxjZbnLejgGaCAnwEe+FWMVshoMmjeLEEEVlQTLiFUHy7vhYyCQVqgbZzQ6zpaGPqPU2woKglKW4RHw== @@ -12849,45 +12874,7 @@ elastic-apm-http-client@11.0.3: semver "^6.3.0" stream-chopper "^3.0.1" -elastic-apm-node@^3.38.0: - version "3.40.0" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.40.0.tgz#ed805ec817db7687ba9a77bcc0db6131e8cbc8cf" - integrity sha512-gs9Z7boZW2o3ZMVbdjoJKXv4F2AcfMh52DW1WxEE/FSFa6lymj6GmCEFywuP8SqdpRZbh6yohJoGOpl7sheNJg== - dependencies: - "@elastic/ecs-pino-format" "^1.2.0" - "@opentelemetry/api" "^1.1.0" - after-all-results "^2.0.0" - async-cache "^1.1.0" - async-value-promise "^1.1.1" - basic-auth "^2.0.1" - cookie "^0.5.0" - core-util-is "^1.0.2" - elastic-apm-http-client "11.0.2" - end-of-stream "^1.4.4" - error-callsites "^2.0.4" - error-stack-parser "^2.0.6" - escape-string-regexp "^4.0.0" - fast-safe-stringify "^2.0.7" - http-headers "^3.0.2" - is-native "^1.0.1" - lru-cache "^6.0.0" - measured-reporting "^1.51.1" - monitor-event-loop-delay "^1.0.0" - object-filter-sequence "^1.0.0" - object-identity-map "^1.0.2" - original-url "^1.2.3" - pino "^6.11.2" - relative-microtime "^2.0.0" - require-in-the-middle "^5.2.0" - semver "^6.3.0" - set-cookie-serde "^1.0.0" - shallow-clone-shim "^2.0.0" - source-map "^0.8.0-beta.0" - sql-summary "^1.0.1" - traverse "^0.6.6" - unicode-byte-truncate "^1.0.0" - -elastic-apm-node@^3.40.1: +elastic-apm-node@^3.38.0, elastic-apm-node@^3.40.1: version "3.40.1" resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.40.1.tgz#ae3669d480fdacf62ace40d12a6f1a3c46b37940" integrity sha512-vdyEZ7BPKJP2a1PkCsg350XXGZj03bwOiGrZdqgflocYxns5QwFbhvMKaVq7hWWWS8/sACesrLLELyQgdOpFsw== @@ -13014,12 +13001,12 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -encoding@^0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= +encoding@^0.1.12, encoding@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: - iconv-lite "~0.4.13" + iconv-lite "^0.6.2" end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4: version "1.4.4" @@ -14789,7 +14776,7 @@ fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-minipass@^2.0.0: +fs-minipass@^2.0.0, fs-minipass@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== @@ -15141,17 +15128,6 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" - integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0, glob@~7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -15164,6 +15140,17 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, gl once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.1: + version "8.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + glob@~7.1.1: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -16097,7 +16084,7 @@ icalendar@0.7.1: resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae" integrity sha1-0NNIZ5X48cXPT4yvrAgbS056Mq4= -iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -16382,11 +16369,6 @@ ip-regex@^2.1.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= -ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - ip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" @@ -18995,6 +18977,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-cache@^7.7.1: + version "7.14.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.1.tgz#8da8d2f5f59827edb388e63e459ac23d6d408fea" + integrity sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA== + lru-queue@0.1: version "0.1.0" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" @@ -19049,6 +19036,28 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +make-fetch-happen@^10.0.4: + version "10.2.1" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" + integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== + dependencies: + agentkeepalive "^4.2.1" + cacache "^16.1.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^7.7.1" + minipass "^3.1.6" + minipass-collect "^1.0.2" + minipass-fetch "^2.0.3" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + socks-proxy-agent "^7.0.0" + ssri "^9.0.0" + make-fetch-happen@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" @@ -19658,7 +19667,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@5.0.1, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2, minimatch@^5.0.1, minimatch@~3.0.2: +minimatch@5.0.1, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2, minimatch@^5.0.1, minimatch@~3.0.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -19697,6 +19706,17 @@ minipass-fetch@^1.3.2: optionalDependencies: encoding "^0.1.12" +minipass-fetch@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" + integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== + dependencies: + minipass "^3.1.6" + minipass-sized "^1.0.3" + minizlib "^2.1.2" + optionalDependencies: + encoding "^0.1.13" + minipass-flush@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" @@ -19718,14 +19738,14 @@ minipass-sized@^1.0.3: dependencies: minipass "^3.0.0" -minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" - integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3, minipass@^3.1.6: + version "3.3.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" + integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== dependencies: yallist "^4.0.0" -minizlib@^2.0.0, minizlib@^2.1.1: +minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== @@ -20013,10 +20033,10 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.13.2, nan@^2.15.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" - integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== +nan@^2.15.0, nan@^2.17.0: + version "2.17.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" + integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== nano-css@^5.2.1: version "5.2.1" @@ -20104,7 +20124,7 @@ nearley@^2.7.10: randexp "0.4.6" semver "^5.4.1" -negotiator@0.6.3, negotiator@^0.6.2: +negotiator@0.6.3, negotiator@^0.6.2, negotiator@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== @@ -20318,10 +20338,10 @@ node-releases@^2.0.6: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== -node-sass@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-7.0.3.tgz#7620bcd5559c2bf125c4fbb9087ba75cd2df2ab2" - integrity sha512-8MIlsY/4dXUkJDYht9pIWBhMil3uHmE8b/AdJPjmFn1nBx9X9BASzfzmsCy0uCCb8eqI3SYYzVPDswWqSx7gjw== +node-sass@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-8.0.0.tgz#c80d52148db0ce88610bcf1e1d112027393c13e1" + integrity sha512-jPzqCF2/e6JXw6r3VxfIqYc8tKQdkj5Z/BDATYyG6FL6b/LuYBNFGFVhus0mthcWifHm/JzBpKAd+3eXsWeK/A== dependencies: async-foreach "^0.1.3" chalk "^4.1.2" @@ -20330,14 +20350,13 @@ node-sass@^7.0.3: get-stdin "^4.0.1" glob "^7.0.3" lodash "^4.17.15" + make-fetch-happen "^10.0.4" meow "^9.0.0" - nan "^2.13.2" + nan "^2.17.0" node-gyp "^8.4.1" - npmlog "^5.0.0" - request "^2.88.0" sass-graph "^4.0.1" stdout-stream "^1.4.0" - "true-case-path" "^1.0.2" + "true-case-path" "^2.2.1" nodemailer@^6.6.2: version "6.6.2" @@ -20455,7 +20474,7 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -npmlog@^5.0.0, npmlog@^5.0.1: +npmlog@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== @@ -22335,16 +22354,16 @@ qs@6.9.7: integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== qs@^6.10.0, qs@^6.5.1, qs@^6.7.0: - version "6.10.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" - integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== dependencies: side-channel "^1.0.4" qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== query-string@^6.13.2: version "6.13.2" @@ -22696,7 +22715,7 @@ react-grid-layout@^1.3.4: react-draggable "^4.0.0" react-resizable "^3.0.4" -react-hook-form@^7.39.7: +react-hook-form@^7.40.0: version "7.40.0" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.40.0.tgz#62bc939dddca88522cd7f5135b6603192ccf7e17" integrity sha512-0rokdxMPJs0k9bvFtY6dbcSydyNhnZNXCR49jgDr/aR03FDHFOK6gfh8ccqB3fl696Mk7lqh04xdm+agqWXKSw== @@ -23394,10 +23413,10 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.10, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: - version "0.13.10" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" - integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== +regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== regenerator-transform@^0.15.0: version "0.15.0" @@ -23704,7 +23723,7 @@ request-progress@^3.0.0: dependencies: throttleit "^1.0.0" -request@^2.44.0, request@^2.88.0: +request@^2.44.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -24116,10 +24135,10 @@ sass-graph@^4.0.1: scss-tokenizer "^0.4.3" yargs "^17.2.1" -sass-loader@^10.3.1: - version "10.3.1" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.3.1.tgz#a45f0d1dd7ea90de7eb099239a18c83dea6e6341" - integrity sha512-y2aBdtYkbqorVavkC3fcJIUDGIegzDWPn3/LAFhsf3G+MzPKTJx37sROf5pXtUeggSVbNbmfj8TgRaSLMelXRA== +sass-loader@^10.4.1: + version "10.4.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.4.1.tgz#bea4e173ddf512c9d7f53e9ec686186146807cbf" + integrity sha512-aX/iJZTTpNUNx/OSYzo2KsjIUQHqvWsAhhUijFjAPdZTEhstjZI9zTNvkTTwsx+uNUJqUwOw5gacxQMx4hJxGQ== dependencies: klona "^2.0.4" loader-utils "^2.0.0" @@ -24535,10 +24554,10 @@ simple-get@^4.0.0, simple-get@^4.0.1: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.10.0.tgz#f20031dd121d3c49e215ef0186a102bece3f89b2" - integrity sha512-2w35xrS5rVtAW0g67LqtxCZN5cdddz/woQRfS0OJXaljXEoTychZ4jnE+CQgra/wX4ZvHeiChTUMenCwfIYEYw== +simple-git@^3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.15.1.tgz#57f595682cb0c2475d5056da078a05c8715a25ef" + integrity sha512-73MVa5984t/JP4JcQt0oZlKGr42ROYWC3BcUZfuHtT3IHKPspIvL0cZBnvPXF7LL3S/qVeVHVdYYmJ3LOTw4Rg== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" @@ -24616,7 +24635,7 @@ slide@~1.1.3: resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= -smart-buffer@^4.1.0: +smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== @@ -24709,13 +24728,22 @@ socks-proxy-agent@^6.0.0: debug "^4.3.1" socks "^2.6.1" -socks@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e" - integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== +socks-proxy-agent@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" + integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.1, socks@^2.6.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== dependencies: - ip "^1.1.5" - smart-buffer "^4.1.0" + ip "^2.0.0" + smart-buffer "^4.2.0" sonic-boom@^1.0.2: version "1.3.0" @@ -25005,6 +25033,13 @@ ssri@^8.0.0, ssri@^8.0.1: dependencies: minipass "^3.1.1" +ssri@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" + integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== + dependencies: + minipass "^3.1.1" + stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" @@ -26237,12 +26272,10 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.1.tgz#a9fd8b0394b0ae8fff82e0633a0a36ccad5b5f86" integrity sha1-qf2LA5Swro//guBjOgo2zK1bX4Y= -"true-case-path@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62" - integrity sha1-fskRMJJHZsf1c74wIMNPj9/QDWI= - dependencies: - glob "^6.0.4" +"true-case-path@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" + integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== ts-debounce@^4.0.0: version "4.0.0" @@ -26632,6 +26665,13 @@ unique-filename@^1.1.1: dependencies: unique-slug "^2.0.0" +unique-filename@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" + integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== + dependencies: + unique-slug "^3.0.0" + unique-slug@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" @@ -26639,6 +26679,13 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +unique-slug@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" + integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== + dependencies: + imurmurhash "^0.1.4" + unique-stream@^2.0.2: version "2.2.1" resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369"